package com.bocloud.ims.service.resource.server;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.bocloud.cmp.entity.enums.BocloudStatus;
import com.bocloud.cmp.entity.enums.NodeAction;
import com.bocloud.cmp.entity.enums.ResourceCategory;
import com.bocloud.cmp.entity.enums.ServerAssetCategory;
import com.bocloud.cmp.entity.enums.installation.Charset;
import com.bocloud.cmp.entity.enums.installation.EndPoints;
import com.bocloud.cmp.entity.enums.installation.InstallImageType;
import com.bocloud.cmp.entity.enums.installation.InstallStatus;
import com.bocloud.cmp.entity.enums.installation.TaskType;
import com.bocloud.cmp.entity.enums.installation.UbuntuVersion;
import com.bocloud.cmp.model.NotifyMessage;
import com.bocloud.cmp.model.installation.HostResult;
import com.bocloud.cmp.model.installation.InstallOptions;
import com.bocloud.cmp.model.installation.InstallPartition;
import com.bocloud.cmp.model.installation.InstallPartitionComparator;
import com.bocloud.cmp.model.installation.IpmiInfo;
import com.bocloud.cmp.model.installation.NetworkDevice;
import com.bocloud.cmp.model.installation.Node;
import com.bocloud.cmp.model.installation.Options;
import com.bocloud.cmp.model.installation.Poller;
import com.bocloud.cmp.model.installation.SensorData;
import com.bocloud.cmp.model.installation.SwitchDevice;
import com.bocloud.common.encrypt.AESEncrptor;
import com.bocloud.common.enums.BoCloudExchange;
import com.bocloud.common.enums.BoCloudMethod;
import com.bocloud.common.enums.BoCloudService;
import com.bocloud.common.enums.ResourceCatalog;
import com.bocloud.common.model.BocloudResult;
import com.bocloud.common.model.GridBean;
import com.bocloud.common.model.OperateResult;
import com.bocloud.common.model.Param;
import com.bocloud.common.model.RequestContext;
import com.bocloud.common.model.Result;
import com.bocloud.common.model.SimpleBean;
import com.bocloud.common.utils.Common;
import com.bocloud.common.utils.GridHelper;
import com.bocloud.common.utils.IDFactory;
import com.bocloud.common.utils.JSONTools;
import com.bocloud.common.utils.MapTools;
import com.bocloud.database.utils.QueryBuilder;
import com.bocloud.ims.common.config.StorageConfig;
import com.bocloud.ims.common.utils.MessageUtils;
import com.bocloud.ims.common.utils.constant.Constant;
import com.bocloud.ims.entity.installation.InstallResult;
import com.bocloud.ims.entity.installation.InstallTask;
import com.bocloud.ims.entity.params.ServerPerformanceParam;
import com.bocloud.ims.entity.resource.Disk;
import com.bocloud.ims.entity.resource.Image;
import com.bocloud.ims.entity.resource.Mdisk;
import com.bocloud.ims.entity.resource.NetworkCard;
import com.bocloud.ims.entity.resource.Resource;
import com.bocloud.ims.entity.resource.Server;
import com.bocloud.ims.entity.resource.ServerAssets;
import com.bocloud.ims.entity.resource.ServerConfig;
import com.bocloud.ims.entity.resource.installation.ServerPci;
import com.bocloud.ims.entity.resource.installation.ServerRaidCtrl;
import com.bocloud.ims.interfaces.installation.CatalogService;
import com.bocloud.ims.interfaces.installation.InstallTaskService;
import com.bocloud.ims.interfaces.installation.RackHdService;
import com.bocloud.ims.interfaces.resource.ServerService;
import com.bocloud.ims.repository.installation.InstallResultRepository;
import com.bocloud.ims.repository.installation.InstallTaskRepository;
import com.bocloud.ims.repository.resource.DiskRepository;
import com.bocloud.ims.repository.resource.ImageRepository;
import com.bocloud.ims.repository.resource.MdiskRepository;
import com.bocloud.ims.repository.resource.NetworkCardRepository;
import com.bocloud.ims.repository.resource.ResourceRepository;
import com.bocloud.ims.repository.resource.ServerAssetsRepository;
import com.bocloud.ims.repository.resource.ServerConfigRepository;
import com.bocloud.ims.repository.resource.ServerRepository;
import com.bocloud.ims.repository.resource.installation.ServerPciRepository;
import com.bocloud.ims.repository.resource.installation.ServerRaidCtrlRepository;
import com.bocloud.ims.service.internal.worker.WorkerService;
import com.bocloud.ims.service.model.cmdb.CmdbInfo;
import com.bocloud.ims.service.utils.CmdbInfoConvertor;
import com.bocloud.ims.service.utils.CmdbMessagePublisher;
import com.bocloud.ims.service.utils.DataStoreUtils;
import com.bocloud.ims.service.utils.DateUtils;
import com.bocloud.ims.service.utils.ExcelUtils;
import com.bocloud.ims.service.utils.IpmiRemoteViewer;
import com.bocloud.ims.service.utils.ResourceEventPublisher;
import com.bocloud.ims.service.utils.TrustManager;
import com.bocloud.service.core.CurrentService;
import com.bocloud.service.lock.BocloudACLock;
import com.bocloud.service.lock.LockFactory;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.Asserts;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.bocloud.ims.common.utils.MessageUtils.CODE_0003;

@Service("installationServerService")
public class InstallationServerServiceImpl extends ServerServiceImpl implements ServerService {
    private static final String HUAWEI = "HuaWei";
    private static final String LANGCHAO = "LangChao";
    private static final String INSPUR = "Inspur";
    private static final int HTTP_OK = 200;
    private static final String HTTPS = "https";
    private static Logger logger = LoggerFactory.getLogger(InstallationServerServiceImpl.class);
    private static String OPERATION = "node.os.install";
    @Autowired
    private ServerRepository serverRepository;
    @Autowired
    private ServerConfigRepository serverConfigRepository;
    @Autowired
    private ServerAssetsRepository serverAssetsRepository;
    @Autowired
    private ImageRepository imageRepository;
    @Autowired
    private CmdbMessagePublisher cmdbMessagePublisher;
    @Autowired
    @Qualifier("ServerService")
    private ServerService serverService;
    @Autowired
    private NetworkCardRepository networkCardRepository;
    @Autowired
    private ResourceRepository resourceRepository;
    @Autowired
    private LockFactory lockFactory;
    @Autowired
    private ServerPciRepository serverPciRepository;
    @Autowired
    private ServerRaidCtrlRepository serverRaidCtrlRepository;
    @Autowired
    private MdiskRepository mdiskRepository;
    @Autowired
    private RabbitAdmin rabbitAdmin;
    @Autowired
    private DiskRepository diskRepository;
    @Autowired
    private CatalogService catalogService;
    @Autowired
    private WorkerService workerService;
    @Autowired
    private ThreadPoolTaskExecutor taskExecutor;
    @Autowired
    private RackHdService rackHdService;
    @Autowired
    private AmqpTemplate amqpTemplate;
    @Autowired
    private ResourceEventPublisher resourceEventPublisher;
    @Autowired
    private InstallTaskService installTaskService;
    @Autowired
    private InstallTaskRepository installTaskRepository;
    @Autowired
    private InstallResultRepository installResultRepository;
    @Autowired
    private CurrentService currentService;
    @Autowired
    private StorageConfig storageConfig;
    @Value("${samba.repo:\\\\localhost\\windowsServer2012}")
    private String smbRepo;
    @Value("${samba.username:bocloud}")
    private String smbUser;
    @Value("${samba.password:bocloud}")
    private String smbPassword;

    public InstallationServerServiceImpl(CurrentService currentService) {
        super(currentService);
    }

    public static String getContent(String ip, String userName, String password) {
        try {
            SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build();
            SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext,
                    NoopHostnameVerifier.INSTANCE);
            HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build();

            HttpPost httpPost = new HttpPost("https://" + ip + "/bmc/php/processparameter.php");
            List<NameValuePair> nameValuePairs = new ArrayList<>();
            nameValuePairs.add(new BasicNameValuePair("user_name", userName));
            nameValuePairs.add(new BasicNameValuePair("check_pwd", password));
            nameValuePairs.add(new BasicNameValuePair("logtype", "0"));
            nameValuePairs.add(new BasicNameValuePair("func", "DirectKVM"));
            nameValuePairs.add(new BasicNameValuePair("IsKvmApp", "0"));
            HttpEntity httpEntity = new UrlEncodedFormEntity(nameValuePairs);
            httpPost.setEntity(httpEntity);
            HttpResponse loginResponse = httpClient.execute(httpPost);
            Asserts.check(loginResponse.getStatusLine().getStatusCode() == 200, "登录失败");

            // HttpGet httpGet = new HttpGet("https://" + ip +
            // "/bmc/pages/remote/kvm.php?kvmway=1");
            // HttpGet httpGet = new HttpGet("https://" + ip +
            // "/bmc/pages/remote/kvm.php?kvmmode=0&kvmway=0");
            HttpGet httpGet = new HttpGet("https://" + ip + "/bmc/pages/remote/kvm.php");
            HttpResponse downloadResponse = httpClient.execute(httpGet);
            Asserts.check(downloadResponse.getStatusLine().getStatusCode() == 200, "下载失败");

            return new String(EntityUtils.toByteArray(downloadResponse.getEntity()));
        } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | IOException e) {
            logger.error("请求http控制台文件内容异常:", e);
        }
        return null;
    }

    private void sendMessage(boolean success, RequestContext context, NotifyMessage message, String content) {
        // 站内信和邮件发送
        amqpTemplate.convertAndSend("event.notice.queue", message);
        // 界面消息
        OperateResult operateResult = new OperateResult(success, content, OPERATION, null,
                OperateResult.OperateCategory.ResourceEvent, context);
        resourceEventPublisher.send(operateResult);
    }

    @Override
    public BocloudResult<String> create(String params, RequestContext context, Boolean isImport, String instance) {
        try {
            Server node = JSON.parseObject(params, Server.class);

            // 检验[名称]和[ipmi的IP]不能与现有数据重复
            BocloudResult checkResult = check(node);
            if (checkResult.isFailed() || null == node.getReboot()) {
                logger.error("新增物理机[{}]校验失败，{}", node.getName(), checkResult.getMessage());
                return new BocloudResult(false, "新增物理机[" + node.getName() + "]校验失败，" + checkResult.getMessage());
            }

            // 检查ipmi信息是否正确
            IpmiInfo ipmiInfo = new IpmiInfo(
                    node.getConfig().getIpmiAddress(),
                    node.getConfig().getIpmiAccount(),
                    node.getConfig().getIpmiPassword(),
                    node.getConfig().getIpmiPort()
            );
            checkResult = workerService.check(ipmiInfo);
            if (checkResult.isFailed()) {
                logger.error("新增物理机[{}]校验失败, ipmi信息[{}]有误.", node.getName(), JSONObject.toJSONString(ipmiInfo));
                return new BocloudResult(false,
                        "新增物理机[" + node.getName() + "]失败, ipmi信息[" + JSONObject.toJSONString(ipmiInfo) + "]有误.");
            }

            // 未被RackHD发现的机器，discovered未false，catalog信息不正常
            node.setDiscovered(false);
            if (null == node.getConfig().getIpmiIps()) {
                List<String> ips = Lists.newArrayList();
                ips.add(node.getConfig().getIpmiAddress());
                node.getConfig().setIpmiIps(JSONObject.toJSONString(ips));
            } else {
                List<String> ips = JSONObject.parseArray(node.getConfig().getIpmiIps(), String.class);
                if (!ips.contains(node.getConfig().getIpmiAddress())) {
                    ips.add(node.getConfig().getIpmiAddress());
                    node.getConfig().setIpmiIps(JSONObject.toJSONString(ips));
                }
            }
            node.setCreatorId(context.getTarget());
            node.setMenderId(context.getTarget());
            node.setGmtCreate(new Date());
            node.setInstance(instance);
            node.setAssetCategory(ServerAssetCategory.BARE_METAL.name());
            node.setPowerState(BocloudStatus.Server.RUNNING.name());

            // 根据是否可以重启进行区分处理
            if (node.getReboot()) {
                // 添加用于物理装机的裸机，pxe重启机器，使用RackHD发现机器
                node.setDiscoverTime(new Date());
                node.setDiscoverUserId(context.getTarget());
                node.setStatus(InstallStatus.DISCOVERYING.getText());
                // 向cmp保存数据
                BocloudResult result = save(node, context);
                if (result.isFailed()) {
                    logger.error("请求cmp保存物理机[{}]信息失败:{}", node.getName(), result);
                    return new BocloudResult(false, "新增物理机[" + node.getName() + "]执行失败：" + result.getMessage());
                }

                node = JSONObject.parseObject(result.getData().toString(), Server.class);
                Server server = node;
                taskExecutor.execute(() -> {
                    String prefix = "新增物理机";
                    String content;
                    // 以任务的形式下发给worker, 设置物理机从网络启动
                    IpmiInfo ipmiInfo1 = new IpmiInfo(
                            server.getConfig().getIpmiAddress(),
                            server.getConfig().getIpmiAccount(),
                            server.getConfig().getIpmiPassword(),
                            server.getConfig().getIpmiPort());
                    BocloudResult invokeResult = workerService.action(ipmiInfo1, NodeAction.networkBoot);
                    if (invokeResult.isSuccess()) {
                        logger.info("{}执行中, 设置机器[{}]从网络启动, 重启中...", prefix, server.getName());
                        // 以任务的形式下发给worker, 设置物理机重启
                        invokeResult = workerService.action(ipmiInfo1, NodeAction.boot);
                        if (invokeResult.isSuccess()) {
                            logger.info("{}执行中, 机器[{}]已经从网络启动, 发现中...", prefix, server.getName());
                            return;
                        } else {
                            logger.error("{}执行中, 机器[{}]执行启动失败：", prefix, server.getName(), invokeResult.getMessage());
                            content = prefix + "失败, 设置机器[" + server.getName() + "]重启失败";
                        }
                    } else {
                        logger.error("{}执行失败, 设置机器[{}]从网络启动失败：{}", prefix, server.getName(), invokeResult.getMessage());
                        content = prefix + "失败, 设置机器[" + server.getName() + "]从pxe启动失败";
                    }
                    NotifyMessage message = new NotifyMessage(context, prefix + "情况通知", content, "管理员您好，" + content,
                            true, false);
                    sendMessage(false, context, message, content);
                });
            } else {
                // 管理并监控那些有业务在跑，不能重启的物理机,做一些监控操作等
                // 调用RackHD新增这台物理机
                String createParams = String.format("{" +
                                "\"autoDiscover\":false," +
                                "\"identifiers\":[]," +
                                "\"name\":\"%s\"," +
                                "\"obms\":[" +
                                    "{" +
                                        "\"config\":{" +
                                            "\"host\":\"%s\"," +
                                            "\"password\":\"%s\"," +
                                            "\"user\":\"%s\"" +
                                        "}," +
                                        "\"nodeId\":\"%s\"," +
                                        "\"service\":\"ipmi-obm-service\"" +
                                    "}" +
                                "]," +
                                "\"type\":\"compute\"" +
                            "}",
                        node.getName(),
                        node.getConfig().getIpmiAddress(),
                        node.getConfig().getIpmiPassword(),
                        new AESEncrptor().decrypt(node.getConfig().getIpmiAccount(), null),
                        node.getName()
                );
                Result result = rackHdService.request(createParams, rackHdService.buildUrl(EndPoints.NODES_V2, null),
                        BoCloudMethod.POST);
                if (result.isFailed()) {
                    logger.error("新增物理机[{}]RackHD失败：{}", node.getName(), result.getMessage());
                    return new BocloudResult(false, "新增物理机[" + node.getName() + "]失败，添加物理机到RackHD失败");
                }
                String nodeId = JSONObject.parseObject(result.getMessage(), JSONObject.class).getString("id");

                // 调用RackHD监控这台物理主机
                String pollerParams = String.format(
                        "{\"type\":\"ipmi\",\"pollInterval\":10000,\"node\":\"%s\",\"config\":{\"command\":\"sdr\"}}",
                        nodeId);
                result = rackHdService.request(pollerParams, rackHdService.buildUrl(EndPoints.NODE_POLLERS_V2, null),
                        BoCloudMethod.POST);
                if (result.isFailed()) {
                    logger.error("新增物理机[{}]监控任务到RackHD失败：{}", node.getName(), result.getMessage());
                    return new BocloudResult(false, "新增物理机[" + node.getName() + "]失败，添加物理机监控任务到RackHD失败");
                }

                // 保存主机信息到cmp数据库
                node.setRackhdNodeId(nodeId);
                node.setDiscoverTime(null);
                node.setStatus(InstallStatus.UNDISCOVERY.getText());
                BocloudResult saveResult = save(node, context);
                if (saveResult.isFailed()) {
                    logger.error("保存新增物理机[{}]信息失败:{}", node.getName(), saveResult);
                    return new BocloudResult(false, "新增物理机[" + node.getName() + "]执行失败：" + saveResult.getMessage());
                }
                node = JSONObject.parseObject(saveResult.getData().toString(), Server.class);
            }
            return new BocloudResult(true, node, "新增物理机[" + node.getName() + "]执行成功");
        } catch (Exception e) {
            logger.error("Create node fail:", e);
            return new BocloudResult(false, "新增物理机异常");
        }
    }

    @Override
    public BocloudResult rediscover(List<Long> nodeIds, RequestContext context) {
        boolean success = true;
        StringBuilder sb = new StringBuilder();
        List<String> successNames = new ArrayList<>();
        for (Long nodeId : nodeIds) {
            BocloudResult result = this.rediscover(nodeId, context);
            String name = (String) result.getData();
            if (result.isFailed()) {
                sb.append("重新发现物理机[").append(name).append("]任务下发失败: ").append(result.getMessage()).append(";");
                success = false;
            } else {
                successNames.add(name);
            }
        }
        if (!successNames.isEmpty()) {
            sb.append("重新发现物理机").append(successNames).append("任务下发成功，正在执行...");
        }
        return new BocloudResult(success, sb.toString());
    }

    @Override
    public BocloudResult rediscover(Long nodeId, RequestContext context) {
        try {
            Server server = get(nodeId);
            if (server == null) {
                logger.warn("重新发现物理机任务下发失败, id为{}的物理机已经被删除", nodeId);
                return new BocloudResult(false, null, "id为" + nodeId + "的物理机已经被删除");
            }
            if (!StringUtils.isNotEmpty(server.getConfig().getIpmiAccount())
                    || !StringUtils.isNotEmpty(server.getConfig().getIpmiPassword())) {
                logger.warn("重新发现物理机任务下发失败,请先编辑物理机{}IPMI信息", server.getName());
                return new BocloudResult(false, server.getName(), "请先编辑物理机" + server.getName() + "IPMI信息");
            }
            // 向数据库更新数据为发现中
            server.setStatus(InstallStatus.DISCOVERYING.getText());
            server.setMenderId(context.getTarget());
            server.setGmtModify(new Date());
            server.setDiscoverUserId(context.getTarget());
            server.setDiscoverTime(new Date());
            server.setPowerState(BocloudStatus.Server.RUNNING.name());
            BocloudResult result = update(server, context);
            if (result.isFailed()) {
                logger.error("请求cmp更新物理机[{}]状态为发现中失败:{}", server.getName(), result);
                return new BocloudResult(false, server.getName(), "请求cmp更新物理机状态为发现中失败");
            }
            taskExecutor.execute(() -> {
                String prefix = "重新发现物理机";
                String content;
                // 以任务的形式下发给worker, 设置物理机从网络启动
                IpmiInfo ipmiInfo = new IpmiInfo(
                        server.getConfig().getIpmiAddress(),
                        server.getConfig().getIpmiAccount(),
                        server.getConfig().getIpmiPassword(),
                        server.getConfig().getIpmiPort()
                );
                BocloudResult invokeResult = workerService.action(ipmiInfo, NodeAction.networkBoot);
                if (invokeResult.isSuccess()) {
                    logger.info("{}执行中, 设置机器[{}]从网络启动, 重启中...", prefix, server.getName());
                    // 以任务的形式下发给worker, 设置物理机重启
                    invokeResult = workerService.action(ipmiInfo, NodeAction.boot);
                    if (invokeResult.isSuccess()) {
                        logger.info("{}执行中, 机器[{}]已经从网络启动, 发现中...", prefix, server.getName());
                        if (server.getRackhdNodeId() != null) {
                            invokeResult = installTaskService.discovery(server.getRackhdNodeId());
                            if (invokeResult.isSuccess()) {
                                logger.info("{}执行中, 调用RackHD发现机器[{}]成功, 发现中...",
                                        prefix + server.getName(), server.getName(), invokeResult.getMessage());
                                return;
                            } else {
                                logger.error("{}执行失败, 调用RackHD发现机器[{}]失败：{}",
                                        prefix + server.getName(), server.getName(), invokeResult.getMessage());
                                content = "调用RackHD重新发现[" + server.getName() + "]失败:" + invokeResult;
                            }
                        } else {
                            logger.warn("{}执行警告, 机器[{}]中尚未记录其在RackHD中的NodeId, 所以并不会提交RackHD重新发现任务，" +
                                    "如果RackHD中存在该机器，则会发现失败！！！",
                                prefix + server.getName(), server.getName(), invokeResult.getMessage());
                            return;
                        }
                    } else {
                        logger.error("{}执行失败, 设置机器[{}]从网络失败：{}",
                            prefix, server.getName(), invokeResult.getMessage());
                        content = prefix + "失败, 设置机器[" + server.getName() + "]重启失败:" + invokeResult;
                    }
                } else {
                    logger.error("{}执行失败, 设置机器[{}]从网络启动失败：{}", prefix, server.getName(), invokeResult.getMessage());
                    content = prefix + "失败, 设置机器[" + server.getName() + "]从pxe启动失败:" + invokeResult;
                }
                NotifyMessage message = new NotifyMessage(context, prefix + "情况通知", content, "管理员您好，" + content, true,
                        false);
                sendMessage(false, context, message, content);
            });
            return new BocloudResult(true, server.getName(), "重新发现物理机[" + server.getName() + "]任务下发成功,正在执行...");
        } catch (Exception e) {
            logger.error("rediscover node fail:", e);
            return new BocloudResult(false, "重新发现物理机异常");
        }
    }

    @Override
    public BocloudResult install(Options options, RequestContext context) {
        return this.install(options, null, context);
    }

    @Override
    public BocloudResult install(Options options, Long taskId, RequestContext context) {
        try {
            logger.info("开始执行装机任务，配置为{}", options.toString());
            // 定义任务开始时间
            Date startTime = new Date();
            // 用imageId查询image信息
            Image image = imageRepository.queryPhysical(options.getImageId());
            if (image == null || image.getDeleted() || !StringUtils.isNotEmpty(image.getOsType())
                    || !StringUtils.isNotEmpty(image.getOsVersion())) {
                logger.error("指定image {} 不存在", options.getImageId());
                return new BocloudResult(false, "指定image " + options.getImageId() + " 不存在");
            }

            // 确认install_task已经入库
            InstallTask installTask;
            if (taskId == null) {
                // 保存装机任务到task表,并获得taskId
                installTask = new InstallTask(options.getImageId(), context.getTarget(), InstallStatus.INSTALLING.getText(),
                        startTime, JSON.toJSONString(options), JSON.toJSONString(options.getNodes()));
                installTask.setOsVersion(image.getOsVersion());
                installTask.setOsType(image.getOsType());
                installTask = installTaskRepository.save(installTask);
                taskId = installTask.getId();
            } else {
                installTask = installTaskRepository.query(taskId);
            }

            // 确定任务名称
            String taskName = this.getTaskName(installTask.getId(), image.getOsType().toUpperCase(),
                    image.getOsVersion());
            if (taskName == null) {
                logger.error("获取任务名称失败");
                return new BocloudResult(false, "获取任务名称失败");
            } else {
                // 设置装机任务名称为 "物理机[a,b]安装CENTOS7"
                installTask.setTaskName(taskName);
                installTaskRepository.update(installTask);
            }

            InstallOptions installOptions = options.getInstallOptions();
            Result configResult = rackHdService.request(null, rackHdService.buildUrl(EndPoints.CONFIG_V2, null),
                    BoCloudMethod.GET);
            if (configResult.isFailed()) {
                if (installTask.getId() != null) {
                    this.taskFail(taskName, installTask.getId(), "获取Rackhd配置失败", installTask, image, context,
                            startTime);
                }
                logger.error("获取Rackhd配置失败:{}", configResult.toString());
                return new BocloudResult(false, "获取Rackhd配置失败");
            }
            JSONObject info = JSONObject.parseObject(configResult.getMessage());

            String imageServer = Constant.HTTP_PREFIX + info.getString(Constant.FS_ADDR) + ":"
                    + info.getInteger(Constant.FS_PORT).toString();

            // 组装installRepo
            String installRepo = imageServer + info.getString(Constant.FS_PATH) + image.getOsType() + "/"
                    + image.getOsVersion();
            if (InstallImageType.WINDOWSSERVER.name().equalsIgnoreCase(image.getOsType())) {
                installOptions.setSmbPassword(smbPassword);
                installOptions.setSmbUser(smbUser);
                installOptions.setSmbRepo(smbRepo);
                installRepo = imageServer + info.getString(Constant.FS_PATH) + Constant.WINPE;
            }

            // 目前看来，从RackHD获取的ImageServer的url是第二块网卡的地址，这个地址和CMP是不通的，所以去掉下面的检测
            // 检查image server是否有common的文件
            /*if (!HttpUtils.online(imageServer + "/" + Constant.COMMON)) {
                if (installTask.getId() != null) {
                    this.taskFail(taskName, installTask.getId(), "装机镜像服务器" + imageServer + "配置错误或者网络延迟较大不适合装机",
                            installTask, image, context, startTime);
                }
                logger.error("装机镜像服务器{}配置错误或者网络延迟较大不适合装机", imageServer);
                return new BocloudResult(false, "装机任务下发失败，装机镜像服务器配置错误或者网络延迟较大不适合装机");
            }*/
            // 检查image server是否有指定image的repo, 即installRepo是否有文件
            /*if (!HttpUtils.online(installRepo)) {
                if (installTask.getId() != null) {
                    this.taskFail(taskName, installTask.getId(), "装机镜像服务器" + installRepo + "配置错误或者网络延迟较大不适合装机",
                            installTask, image, context, startTime);
                }
                logger.error("装机镜像{}配置错误或者网络延迟较大不适合装机", installRepo);
                return new BocloudResult(false, "装机任务下发失败，装机镜像配置错误或者网络延迟较大不适合装机");
            }*/

            // image server没问题,设置装机参数
            installOptions.setRepo(installRepo);
            installOptions.setVersion(image.getOsVersion());

            // ubuntu的操作系统, 修改装机kargs, 设置网卡名命名规则为eth0,eth1,eth2...
            if (InstallImageType.UBUNTU.name().equalsIgnoreCase(image.getOsType())) {
                installOptions.setBaseUrl("install/netboot/ubuntu-installer/amd64");
                Map<String, String> kargs = new HashMap<>(3);
                // 前端已经限制，ubuntu的版本只能为trusty, xenial, artful
                if (UbuntuVersion.trusty.name().equalsIgnoreCase(image.getOsVersion())) {
                    kargs.put("live-installer/net-image", installRepo + "/install/filesystem.squashfs");
                } else {
                    kargs.put("live-installer/net-image", installRepo + "/ubuntu/install/filesystem.squashfs");
                }
                kargs.put("net/ifnames", "0");
                kargs.put("biosdevname", "0");
                installOptions.setKargs(kargs);
                installOptions.setOsName("ubuntu");
            }

            // 调整自定义分区的设置
            if (null != installOptions.getInstallPartitions()) {
                try {
                    List<InstallPartition> installPartitions = Lists.newArrayList();
                    InstallPartition rootPartition = null;
                    for (InstallPartition partition : installOptions.getInstallPartitions()) {
                        if ("/".equals(partition.getMountPoint())) {
                            // 根分区的大小前端传值为auto,无需调整单位
                            if (InstallImageType.UBUNTU.name().equalsIgnoreCase(image.getOsType())) {
                                // RackHD2.51版本后ubuntu的安装使用partman进行分区
                                // 需要把根分区的大小设置为4096
                                // 且放到list的最后
                                partition.setSize(String.valueOf(4096));
                            }
                            rootPartition = partition;
                        } else {
                            // 调整自定义分区的单位GB改为MB
                            Integer size = Integer.parseInt(partition.getSize()) * 1024;
                            partition.setSize(String.valueOf(size));
                            installPartitions.add(partition);
                        }
                    }
                    if (InstallImageType.UBUNTU.name().equalsIgnoreCase(image.getOsType())) {
                        // RackHD2.51版本后ubuntu的安装使用partman进行分区
                        // 需要把其他分区的大小按顺序排列，从小到大，根分区最后(使用剩余所有空间)
                        // https://www.bishnet.net/tim/blog/2015/01/29/understanding-partman-autoexpert_recipe/
                        InstallPartitionComparator comparator = new InstallPartitionComparator();
                        installPartitions.sort(comparator);
                    }
                    installPartitions.add(rootPartition);
                    installOptions.setInstallPartitions(installPartitions);
                    logger.info("自定义分区设置为:" + JSON.toJSONString(installOptions.getInstallPartitions()));
                } catch (Exception e) {
                    this.taskFail(taskName, installTask.getId(), "自定义分区设置处理异常:" + e, installTask, image, context,
                            startTime);
                    return new BocloudResult(false, "装机任务下发失败，自定义分区设置处理异常");
                }
            }

            boolean success = true;
            for (Long nodeId : options.getNodes()) {
                try {
                    Server server = get(nodeId);
                    if (server == null || null == server.getRackhdNodeId()) {
                        success = false;
                        // 更新节点信息
                        this.nodeFail(TaskType.OSINSTALL, server, image, context.getTarget(), nodeId,
                                "id为[" + nodeId + "]的物理机不存在或RackHD不存在该物理机", startTime, context);
                        logger.error("id为{}的物理机不存在或RackHD不存在该物理机", nodeId);
                        continue;
                    }
                    // 确认ipmi信息正确无误
                    IpmiInfo ipmiInfo = new IpmiInfo(
                            server.getConfig().getIpmiAddress(),
                            server.getConfig().getIpmiAccount(),
                            server.getConfig().getIpmiPassword(),
                            server.getConfig().getIpmiPort()
                    );
                    BocloudResult checkResult = workerService.check(ipmiInfo);
                    if (checkResult.isFailed()) {
                        logger.error("物理机{}的ipmi信息[{}]有误.", server.getName(), JSONObject.toJSONString(ipmiInfo));
                        success = false;
                        // 更新节点信息
                        this.nodeFail(TaskType.OSINSTALL, server, image, nodeId, installTask.getId(), "ipmi信息有误",
                                startTime, context);
                        continue;
                    }

                    // 处理ESXI的情况
                    if (InstallImageType.ESXI.name().equalsIgnoreCase(image.getOsType())) {
                        modify4Esxi(installOptions, server.getIdentifier());
                    }

                    BocloudResult result;
                    if (options.getRaidOptions() == null) {
                        result = installTaskService.install(server.getRackhdNodeId(), installOptions,
                                InstallImageType.valueOf(image.getOsType().toUpperCase()), server, taskId, startTime);
                        if (result.isFailed()) {
                            success = false;
                            // 更新节点信息
                            this.nodeFail(TaskType.OSINSTALL, server, image, nodeId, installTask.getId(),
                                    result.getMessage(), startTime, context);
                            logger.error("物理机[{}]装机失败,执行装机任务失败:{}", server.getName(), result.toString());
                            continue;
                        }
                    } else {
                        result = installTaskService.raid(server, options, taskId, startTime);
                        if (result.isFailed()) {
                            success = false;
                            // 更新节点信息
                            this.nodeFail(TaskType.RAID, server, image, nodeId, installTask.getId(),
                                    result.getMessage(), startTime, context);
                            logger.error("物理机[{}]装机失败,RAID配置失败:{}", server.getName(), result.toString());
                            continue;
                        }
                    }

                    // 更新节点信息
                    server.setImageId(image.getId());
                    server.setMenderId(context.getTarget());
                    server.setOs(image.getOsType());
                    server.setOsCategory(
                            InstallImageType.WINDOWSSERVER.name().equals(image.getOsType()) ? "WINDOWS" : "LINUX");
                    server.setGmtModify(startTime);
                    server.setStatus(InstallStatus.INSTALLING.getText());
                    server.setPowerState(BocloudStatus.Server.RUNNING.name());
                    update(server, context);
                } catch (Exception e) {
                    success = false;
                    taskFail(taskName, taskId, "装机异常:" + e.getMessage(), installTask, image, context, startTime);
                    logger.error("装机异常:", e);
                }
            }
            if (!success) {
                // 如果有节点装机任务下发失败,设置当前任务为失败
                installTask = installTaskRepository.query(installTask.getId());
                installTask.setStatus(InstallStatus.INSTALL_FAILED.getText());
                installTask.setResult("存在装机失败的任务");
                installTask.setGmtFinish(new Date());
                installTaskRepository.update(installTask);
            }
            return new BocloudResult(success, success ? "装机任务下发成功" : "装机任务下发失败");
        } catch (Exception e) {
            logger.error("装机异常:", e);
            return new BocloudResult(false, "装机异常");
        }
    }

    private void taskFail(String taskName, Long taskId, String result, InstallTask installTask, Image image,
                          RequestContext context, Date startTime) {
        try {
            List<Long> nodeIds = JSONObject.parseArray(installTask.getNodes(), Long.class);

            for (Long nodeId : nodeIds) {
                Server server = get(nodeId);

                // 更新节点信息
                server.setImageId(image.getId());
                server.setMenderId(context.getTarget());
                server.setOs(image.getOsType());
                server.setOsCategory(
                        InstallImageType.WINDOWSSERVER.name().equals(image.getOsType()) ? "WINDOWS" : "LINUX");
                server.setGmtModify(new Date());
                server.setStatus(InstallStatus.INSTALL_FAILED.getText());
                update(server, context);

                // 保存install_result表
                InstallResult installResult = new InstallResult(nodeId, taskId, TaskType.OSINSTALL.name(), null,
                        server.getName() + "安装[" + image.getOsType().toUpperCase() + "]", InstallStatus.INSTALL_FAILED.getText(),
                        result, startTime, new Date());
                installResultRepository.save(installResult);
            }

            // 更新install_task表
            installTask.setResult(result);
            installTask.setStatus(InstallStatus.INSTALL_FAILED.getText());
            installTask.setGmtFinish(new Date());
            this.installTaskRepository.update(installTask);

            // 发送界面消息
            String content = "批量装机任务" + taskName + "失败";
            logger.warn(content + ",原因" + result);
            NotifyMessage msg = new NotifyMessage(context, "物理主机装机情况通知", content, "管理员您好，" + content, true, false);
            sendMessage(false, context, msg, content);
        } catch (Exception e) {
            logger.error("taskFail execute exception:", e);
        }
    }

    private void nodeFail(TaskType type, Server server, Image image, Long nodeId, Long taskId, String message,
                          Date startTime, RequestContext context) {
        try {
            // 更新节点信息
            server.setImageId(image.getId());
            server.setMenderId(context.getTarget());
            server.setOs(image.getOsType());
            server.setOsCategory(InstallImageType.WINDOWSSERVER.name().equals(image.getOsType()) ? "WINDOWS" : "LINUX");
            server.setGmtModify(new Date());
            server.setStatus(InstallStatus.INSTALL_FAILED.getText());
            update(server, context);
            // 发送界面消息
            String content = "物理机[" + server.getName() + "]装机失败";
            logger.warn(content + ",原因" + message);
            NotifyMessage msg = new NotifyMessage(context, "物理主机装机情况通知", content + ",原因" + message,
                    "管理员您好，" + content + ",原因" + message, true, false);
            sendMessage(false, context, msg, content);

            String taskName = null;
            if (TaskType.OSINSTALL.equals(type)) {
                taskName = "物理机[" + server.getName() + "]安装[" + image.getOsType() + "]";
            } else if (TaskType.RAID.equals(type)) {
                taskName = "物理机[" + server.getName() + "]配置RAID";
            }
            InstallResult installResult = new InstallResult(nodeId, taskId, type.name(), null, taskName,
                    InstallStatus.INSTALL_FAILED.getText(), message, startTime, new Date());
            installResultRepository.save(installResult);
        } catch (Exception e) {
            logger.error("nodeFail execute exception:", e);
        }
    }

    private String getTaskName(Long taskId, String imageType, String imageVersion) {
        try {
            InstallTask installTask = installTaskRepository.query(taskId);
            List<Long> nodeIds = JSONObject.parseArray(installTask.getNodes(), Long.class);
            StringBuilder taskName = new StringBuilder("物理机[");
            for (Long nodeId : nodeIds) {
                Server server = get(nodeId);

                taskName.append(server.getName()).append(",");
            }
            taskName.deleteCharAt(taskName.length() - 1);
            taskName.append("]安装").append(imageType).append(imageVersion);
            return taskName.toString();
        } catch (Exception e) {
            logger.error("获取任务名称异常:", e);
            return null;
        }
    }

    private void modify4Esxi(InstallOptions installOptions, String identifier) {
        logger.info("before modify for esxi, options={}", JSONObject.toJSONString(installOptions));
        // 将identifier绑定vSwitch0
        List<SwitchDevice> devices = installOptions.getSwitchDevices();
        for (SwitchDevice device : devices) {
            if (Constant.DEFAULT_VSWITCH.equals(device.getSwitchName())) {
                List<String> uplinks = device.getUplinks();
                uplinks.add(identifier);
                // uplinks去重
                Set<String> set = new HashSet<>(uplinks);
                uplinks = new ArrayList<>(set);
                device.setUplinks(uplinks);
            }
        }
        // 其他网卡绑定vSwitch的情况根据前台参数加入
        for (SwitchDevice device : devices) {
            if (installOptions.getNetworkDevices() != null) {
                for (NetworkDevice networkDevice : installOptions.getNetworkDevices()) {
                    if (device.getSwitchName().equals(networkDevice.getEsxSwitchName())) {
                        List<String> uplinks = device.getUplinks();
                        uplinks.add(networkDevice.getDevice());
                        device.setUplinks(uplinks);
                    }
                }
            }
        }
        installOptions.setSwitchDevices(devices);

        // 将identifier加入networkDevices
        List<NetworkDevice> networkDevices = Lists.newArrayList();
        // 设置dhcp的网卡为第一块网卡
        NetworkDevice networkDevice = new NetworkDevice(identifier, Constant.DEFAULT_VSWITCH);
        networkDevices.add(networkDevice);
        if (installOptions.getNetworkDevices() != null) {
            networkDevices.addAll(installOptions.getNetworkDevices());
        }
        installOptions.setNetworkDevices(networkDevices);
        // 默认是进入维护模式，如果不需要维护模式，则使用postInstallCommands退出维护模式
        if (!installOptions.getMaintenanceMode()) {
            List<String> commands = new ArrayList<>();
            commands.add("esxcli system maintenanceMode set --enable no");
            installOptions.setPostInstallCommands(commands);
        }
        installOptions.setMaintenanceMode(null);
        installOptions.setClearDisk("alldrives");
        logger.info("after modify for esxi, options={}", JSONObject.toJSONString(installOptions));
    }

    /**
     * 批量导入物理机
     * <p>
     * 通过Excel文件，先将Excel文件上传至NFS。
     * </p>
     *
     * @author sxp
     * @since Nov 13, 2017
     */
    @Override
    public BocloudResult batchImport(JSONObject json, RequestContext context) {
        try {
            // 根据文件路径读取Excel
            String fileName = json.getString("fileName");
            if (StringUtils.isEmpty(fileName)) {
                return new BocloudResult(false, "请上传批量导入的Excel文件");
            }

            String filePath = storageConfig.getPath();

            // 检查Excel文件是否存在
            java.io.File excelFile = new File(filePath + fileName);
            if (!excelFile.exists()) {
                return new BocloudResult(false, "上传的批量导入Excel文件[" + filePath + fileName + "]不存在");
            }

            InputStream is = new FileInputStream(filePath + fileName);

            boolean ignoreFirstRow = true;
            String[] columns = {"name", "ipmiIp", "ipmiPort", "ipmiUser", "ipmiPassword", "reboot", "remark"};

            List<Map<String, Object>> dataOfExcel = ExcelUtils.readContent(is, ignoreFirstRow, columns);
            if (dataOfExcel == null || dataOfExcel.isEmpty()) {
                return new BocloudResult(false, "没有读取到Excel文件[" + fileName + "]中的内容，请检查上传的文件是否正确");
            }

            // 检验Excel文件内容
            // name,ipmiIp,ipmiPort,ipmiUser,ipmiPassword,reboot非空
            BocloudResult result;
            // 前6列
            String[] notNullCols = Arrays.copyOfRange(columns, 0, 6);
            for (String col : notNullCols) {
                result = DataStoreUtils.checkNotNull(dataOfExcel, col);
                if (!result.isSuccess()) {
                    return new BocloudResult(false, "批量导入物理机失败：" + result.getMessage());
                }
            }

            // name,ipmiIp是否有重复
            // 前2列
            String[] notRepeateCols = Arrays.copyOfRange(columns, 0, 2);
            for (String col : notRepeateCols) {
                result = DataStoreUtils.checkNotRepeat(dataOfExcel, col);
                if (!result.isSuccess()) {
                    return new BocloudResult(false, "批量导入物理机失败：" + result.getMessage());
                }
            }
            taskExecutor.execute(() -> {
                // 循环调用手动新增机器create()方法，成功和失败的结果返回给前端
                String message;
                StringBuilder errorMsg = new StringBuilder();
                Server node;
                int successNum = 0;
                int failNum = 0;
                int rowNum = 0;

                for (Map<String, Object> row : dataOfExcel) {
                    node = new Server();
                    try {
                        node.setName((String) row.get("name"));
                        node.setRemark((String) row.get("remark"));
                        if (row.get("reboot") instanceof Boolean) {
                            node.setReboot((Boolean) row.get("reboot"));
                        } else {
                            node.setReboot(Boolean.parseBoolean((String) row.get("reboot")));
                        }
                        ServerConfig config = new ServerConfig();
                        config.setIpmiAddress((String) row.get("ipmiIp"));
                        config.setIpmiAccount((String) row.get("ipmiUser"));
                        config.setIpmiPassword((String) row.get("ipmiPassword"));
                        if (row.get("ipmiPort") instanceof Double) {
                            config.setIpmiPort(((Double) row.get("ipmiPort")).intValue());
                        } else if (row.get("ipmiPort") instanceof Integer) {
                            config.setIpmiPort(Integer.parseInt((String) row.get("ipmiPort")));
                        } else {
                            failNum++;
                            errorMsg.append("保存第[").append(++rowNum).append("]行数据失败,原因为[");
                            errorMsg.append(row.get("ipmiPort")).append("类型错误");
                            errorMsg.append("];");
                            continue;
                        }
                        node.setConfig(config);
                    } catch (Exception e) {
                        failNum++;
                        errorMsg.append("保存第[").append(++rowNum).append("]行数据失败,原因为[");
                        errorMsg.append("输入物理机器配置").append(JSONObject.toJSONString(row)).append("不合法");
                        errorMsg.append("];");
                        continue;
                    }
                    // 检查配置数据的位数
                    if (node.getName().length() > 20 || node.getConfig().getIpmiAddress().length() > 64
                            || node.getConfig().getIpmiAccount().length() > 64
                            || node.getConfig().getIpmiPassword().length() > 64 || node.getRemark().length() > 255) {
                        failNum++;
                        errorMsg.append("保存第[").append(++rowNum).append("]行数据失败,原因为[");
                        errorMsg.append("输入物理机器配置").append(JSONObject.toJSONString(row)).append("不合法");
                        errorMsg.append("];");
                        continue;
                    }
                    String ipmiPassword = node.getConfig().getIpmiPassword();
                    AESEncrptor aesEncrptor = new AESEncrptor();
                    try {
                        ipmiPassword = aesEncrptor.encrypt(ipmiPassword, null);
                    } catch (Exception ignored) {
                    }
                    node.getConfig().setIpmiPassword(ipmiPassword);
                    BocloudResult createResult = this.create(JSON.toJSONString(node), context, false, currentService.getService().getInstance());
                    if (createResult.isSuccess()) {
                        successNum++;
                    } else {
                        failNum++;
                        errorMsg.append("保存第[").append(++rowNum).append("]行数据失败,原因为[");
                        errorMsg.append(createResult.getMessage());
                        errorMsg.append("];");
                    }
                }

                if (errorMsg.length() > 0) {
                    // 删除最后一个
                    errorMsg.deleteCharAt(errorMsg.length() - 1);
                }

                int size = dataOfExcel.size();
                if (successNum == size) {
                    message = "批量导入物理机全部成功。";
                    NotifyMessage notifyMessage = new NotifyMessage(context, "物理主机批量导入通知", message, "管理员您好，" + message,
                            true, false);
                    sendMessage(true, context, notifyMessage, message);
                    return;
                } else if (failNum == size) {
                    message = "批量导入物理机全部失败，请修改后重新导入。";
                } else {
                    message = "批量导入物理机，共[" + size + "]条数据，成功[" + successNum + "]条，失败[" + failNum + "]条，请修改后重新导入。";
                }
                message += errorMsg.toString();

                NotifyMessage notifyMessage = new NotifyMessage(context, "物理主机批量导入通知", message, "管理员您好，" + message,
                        true, false);
                sendMessage(false, context, notifyMessage, message);
            });
            return new BocloudResult(true, "批量导入任务已经下发，正在执行……");
        } catch (Exception e) {
            logger.error("batchImport nodes failed:", e);
            return new BocloudResult(false, "批量导入物理机异常");
        }
    }

    /**
     * 批量删除物理机
     *
     * @param json    包含待删除物理机的id的数组
     * @param context 当前用户的ID
     * @author sxp
     * @since Nov 21, 2017
     */
    @Override
    public BocloudResult batchRemove(JSONObject json, RequestContext context) {
        String message;
        try {
            Object ids = json.get(Common.ID);
            JSONArray idArray = (JSONArray) JSON.toJSON(ids);
            int size = idArray.size();

            StringBuilder errorMsg = new StringBuilder();
            Long id;
            BocloudResult result;
            int successNum = 0;
            int failNum = 0;

            for (int i = 0; i < size; i++) {
                id = idArray.getLong(i);

                result = this.remove(id, true, context);
                if (result.isSuccess()) {
                    successNum++;
                } else {
                    failNum++;
                    errorMsg.append("删除第[").append(i + 1).append("]个物理机失败,原因为[");
                    errorMsg.append(result.getMessage());
                    errorMsg.append("];");
                }
            }

            if (errorMsg.length() > 0) {
                errorMsg.deleteCharAt(errorMsg.length() - 1);
            }

            if (successNum == size) {
                message = "批量删除物理机全部成功。";
                return new BocloudResult(true, message);
            } else if (failNum == size) {
                message = "批量删除物理机全部失败。";
            } else {
                message = "批量删除物理机，共[" + size + "]个物理机，成功[" + successNum + "]个，失败[" + failNum + "]个。";
            }

            message += errorMsg.toString();
            return new BocloudResult(false, message);

        } catch (Exception e) {
            logger.error("batchRemove nodes failed:", e);
            message = "批量删除物理机失败：" + e.getMessage();
            return new BocloudResult(false, message);
        }
    }

    private String parseAction(NodeAction action) {
        String actionString;
        switch (action) {
            case on:
                actionString = "开机";
                break;
            case off:
                actionString = "关机";
                break;
            case reset:
                actionString = "重启";
                break;
            default:
                actionString = action.name();
                break;
        }
        return actionString;
    }

    private BocloudStatus.Server parseActionStatus(NodeAction action) {
        BocloudStatus.Server status = null;
        switch (action) {
            case on:
                status = BocloudStatus.Server.STARTING;
                break;
            case off:
                status = BocloudStatus.Server.STOPPING;
                break;
            case reset:
                status = BocloudStatus.Server.RESTARTING;
                break;
            default:
                break;
        }
        return status;
    }

    public BocloudResult action(Long id, RequestContext context, NodeAction action) {
        try {
            Server node = get(id);
            if (node == null || node.getDeleted()) {
                logger.error("id为[{}]的物理机不存在，无法执行开关机操作", id);
                return new BocloudResult(false, "id为[" + id + "]的物理机不存在");
            }
            if (!StringUtils.isNotEmpty(node.getConfig().getIpmiAddress())
                    || !StringUtils.isNotEmpty(node.getConfig().getIpmiAccount())
                    || !StringUtils.isNotEmpty(node.getConfig().getIpmiPassword())) {
                return new BocloudResult(false, MessageUtils.get(CODE_0003, node.getName()));
            }
            String operate = parseAction(action);
            BocloudStatus.Server tmpStatus = parseActionStatus(action);
            node.setPowerState(tmpStatus.name());
            BocloudResult result = update(node, context);
            if (result.isFailed()) {
                logger.warn("请求cmp更新物理机[{}]状态为[{}]失败:{}", node.getName(), tmpStatus.name(), result);
                return new BocloudResult(true, "物理机[" + node.getName() + "]" + operate + "任务执行失败，更新cmp中物理机状态失败");
            }

            // 申明临时队列
            String queue = IDFactory.instance().uuid();
            String EXCHANGE = BoCloudExchange.Default.DIRECT.getExchange();
            Map<String, Object> arguments = MapTools.simpleMap("x-ha-policy", "all");
            rabbitAdmin.declareQueue(new Queue(queue, false, false, true, arguments));
            rabbitAdmin.declareBinding(new Binding(queue, Binding.DestinationType.QUEUE, EXCHANGE, queue, arguments));

            IpmiInfo ipmiInfo = new IpmiInfo(
                    node.getConfig().getIpmiAddress(),
                    node.getConfig().getIpmiAccount(),
                    node.getConfig().getIpmiPassword(),
                    node.getConfig().getIpmiPort()
            );
            result = workerService.action(ipmiInfo, action, queue);
            if (result.isFailed()) {
                rabbitAdmin.deleteQueue(queue);
                logger.error("请求worker执行物理机[{}]{}任务失败", node.getName(), operate);
                return new BocloudResult(true, "物理机[" + node.getName() + "]" + operate + "任务下发失败");
            }
            taskExecutor.execute(() -> {
                String content;
                boolean success = false;
                try {
                    Message message = rabbitAdmin.getRabbitTemplate().receive(queue, 1000 * 30);
                    if (message == null) {
                        logger.error("get callback from remote worker by queue [{}] timeout", queue);
                        content = "获取物理机" + node.getName() + operate + "任务结果消息超时！";
                    } else {
                        logger.info("get callback from remote worker by queue [{}]", queue);
                        String msg = new String(message.getBody(), Charset.UTF8.name());
                        BocloudResult bocloudResult = JSONObject.parseObject(msg, BocloudResult.class);
                        if (bocloudResult.isSuccess()) {
                            String status;
                            if (action == NodeAction.on || action == NodeAction.reset) {
                                status = BocloudStatus.Server.RUNNING.name();
                            } else {
                                status = BocloudStatus.Server.STOPPED.name();
                            }
                            node.setPowerState(status);
                            bocloudResult = update(node, context);
                            logger.info("调用cmp更新物理机{}电源状态结果:{}", node.getName(), bocloudResult);
                            if (bocloudResult.isSuccess()) {
                                success = true;
                                content = "物理机[" + node.getName() + "]" + operate + "成功";
                            } else {
                                logger.error("更新cmp数据失败:{}", bocloudResult);
                                content = "物理机[" + node.getName() + "]电源状态失败";
                            }
                        } else {
                            content = "调用worker对物理机" + node.getName() + operate + "失败:" + getError(bocloudResult);
                        }
                    }
                } catch (Exception e) {
                    logger.error("callback data transfer exception!", e);
                    content = "获取物理机开关机结果异常!";
                } finally {
                    rabbitAdmin.deleteQueue(queue);
                }
                logger.info("物理主机[{}]开关机情况结果为: {}", node.getName(), content);
                NotifyMessage message = new NotifyMessage(context, "物理主机开关机情况通知", content, "管理员您好，" + content, true,
                        false);
                sendMessage(success, context, message, content);
            });
            return new BocloudResult(true, "物理机[" + node.getName() + "]" + operate + "任务下发成功");
        } catch (Exception e) {
            logger.error("任务下发异常:", e);
            return new BocloudResult(false, "任务下发异常");
        }
    }

    private String getError(BocloudResult workerResult) {
        try {
            List<HostResult> results = JSONObject.parseArray(JSON.toJSONString(workerResult.getData()),
                    HostResult.class);
            // 开关机的target都是worker机器，所以结果里面也只有一个结果，直接get 0
            JSONObject message = JSONObject.parseObject(results.get(0).getMessage(), JSONObject.class);
            return message.getString("msg");
        } catch (Exception e) {
            logger.error("worker返回数据{}格式错误:", workerResult, e);
            return "worker返回数据格式错误";
        }
    }

    @Override
    public BocloudResult start(Long id, RequestContext context) {
        return action(id, context, NodeAction.on);
    }

    @Override
    public BocloudResult stop(Long id, RequestContext context) {
        return action(id, context, NodeAction.off);
    }

    @Override
    public BocloudResult restart(Long id, RequestContext context) {
        return action(id, context, NodeAction.reset);
    }

    @Override
    public BocloudResult performance(String params, Long id, RequestContext context) {
        try {
            Server node = get(id);
            if (node == null || node.getDeleted()) {
                logger.error("id为[{}]的物理机不存在，无法获取监控数据", id);
                return new BocloudResult(false, "id为[" + id + "]的物理机不存在");
            }
            Result pollerResult = rackHdService.request(null, rackHdService.buildUrl(EndPoints.NODE_POLLERS_V2, null),
                    BoCloudMethod.GET);
            if (pollerResult.isFailed()) {
                logger.error("从rackhd获取poller失败:{}", pollerResult.toString());
                return new BocloudResult(false, "从rackhd获取poller失败:" + pollerResult.getErrorMsg());
            }

            List<Poller> pollers = JSONObject.parseArray(pollerResult.getMessage(), Poller.class);
            String pollerId = null;
            for (Poller poller : pollers) {
                String nodeRef = String.format(EndPoints.NODE_V2.getValue(), node.getRackhdNodeId());
                if (nodeRef.equals(poller.getNode())
                        && Constant.IPMI_CMD_SDR.equals(poller.getConfig().get(Constant.COMMAND))) {
                    pollerId = poller.getId();
                }
            }
            if (!StringUtils.isNotEmpty(pollerId)) {
                logger.warn("Rackhd未监控物理机[{}]", node.getName());
                return new BocloudResult(true, Lists.newArrayList(), "暂无物理机[" + node.getName() + "]监控数据");
            }
            Result dataResult = rackHdService.request(null,
                    rackHdService.buildUrl(EndPoints.NODE_POLLERS_DATA_CUR_V2, pollerId), BoCloudMethod.GET);
            if (dataResult.isFailed()) {
                logger.error("从rackhd获取物理机[{}]poller data失败:{}", node.getName(), dataResult.toString());
                return new BocloudResult(false, "从RackHD获取物理机[" + node.getName() + "]监控数据失败");
            }
            if (!StringUtils.isNotEmpty(dataResult.getMessage())) {
                logger.warn("Rackhd未监控物理机[{}]", node.getName());
                return new BocloudResult(true, Lists.newArrayList(), "暂无物理机[" + node.getName() + "]监控数据");
            }
            List<JSONObject> allData = JSONObject.parseArray(dataResult.getMessage(), JSONObject.class);
            if (allData.isEmpty()) {
                logger.warn("从rackhd获取物理机[{}]poller data为空", node.getName());
                return new BocloudResult(true, Lists.newArrayList(), "暂无物理机[" + node.getName() + "]监控数据");
            }
            List<SensorData> sdrData = JSONObject.parseArray(allData.get(0).getString(Constant.IPMI_CMD_SDR),
                    SensorData.class);
            List<SensorData> fan = Lists.newArrayList();
            List<SensorData> voltage = Lists.newArrayList();
            List<SensorData> temperature = Lists.newArrayList();
            for (SensorData sensor : sdrData) {
                switch (sensor.getSensorType()) {
                    case Constant.FAN:
                        fan.add(new SensorData(sensor.getSensorId(), sensor.getSensorReading(),
                                sensor.getSensorReadingUnits(), sensor.getUpperCritical(), sensor.getLowerCritical()));
                        break;
                    case Constant.VOLTAGE:
                        voltage.add(new SensorData(sensor.getSensorId(), sensor.getSensorReading(),
                                sensor.getSensorReadingUnits(), sensor.getUpperCritical(), sensor.getLowerCritical()));
                        break;
                    case Constant.POWER:
                        voltage.add(new SensorData(sensor.getSensorId(), sensor.getSensorReading(),
                                sensor.getSensorReadingUnits(), sensor.getUpperCritical(), sensor.getLowerCritical()));
                        break;
                    case Constant.TEMPERATURE:
                        temperature.add(new SensorData(sensor.getSensorId(), sensor.getSensorReading(),
                                sensor.getSensorReadingUnits(), sensor.getUpperCritical(), sensor.getLowerCritical()));
                        break;
                    default:
                        break;
                }
            }
            Map<String, Object> data = Maps.newHashMap();
            data.put(Constant.FAN, fan);
            data.put(Constant.VOLTAGE, voltage);
            data.put(Constant.TEMPERATURE, temperature);
            logger.info("物理机{}监控数据为:{}", node.getName(), JSONObject.toJSONString(data));
            return new BocloudResult(true, data, "获取物理机[" + node.getName() + "]装机监控数据成功");
        } catch (Exception e) {
            logger.error("获取监控数据失败:", e);
            return new BocloudResult(false, "获取监控数据失败");
        }
    }

    /**
     * @since 2018-03-16 modify by sxp 修改ipmi信息后,调用cmp服务同步更新ipmi信息
     */
    @Override
    public BocloudResult setIpmiInfo(IpmiInfo info, RequestContext context) {
        try {
            String cipherPassword = info.getPassword();
            String plainPassword = new AESEncrptor().decrypt(info.getPassword(), null);

            // 检查ipmi信息是否正确
            BocloudResult checkResult = workerService.check(info);
            if (checkResult.isFailed()) {
                logger.error("ipmi信息[{}]有误.", JSONObject.toJSONString(info));
                return new BocloudResult(false, "更新ipmi信息失败");
            }
            // 检查该节点是否存在
            Server node = get(info.getNodeId());
            if (node == null) {
                logger.error("该节点{}不存在", info.getNodeId());
                return new BocloudResult(false, "更新ipmi信息失败, 物理机不存在");
            }

            // rackhd上有改机器，则更新obm设置
            if (StringUtils.isNotEmpty(node.getRackhdNodeId())) {
                // 组装obm信息
                Map<String, Object> obmInfo = Maps.newHashMap();
                obmInfo.put(Constant.NODE_ID, node.getRackhdNodeId());
                obmInfo.put(Constant.CONFIG, new IpmiInfo(
                        info.getNodeId(),
                        info.getHost(),
                        info.getUser(),
                        plainPassword,
                        info.getPort()
                ));
                obmInfo.put(Constant.SERVICE, Constant.OBM_SERVICE);

                // 获取node信息
                Result getResult = rackHdService.request(null,
                        rackHdService.buildUrl(EndPoints.NODE_V2, node.getRackhdNodeId()), BoCloudMethod.GET);
                if (getResult.isFailed()) {
                    logger.error("获取RackHd节点信息失败{}", getResult.getMessage());
                    return new BocloudResult(false, "更新ipmi信息失败");
                }

                Node rackHdNode = JSONObject.parseObject(getResult.getMessage(), Node.class);
                // 暂时只支持一个节点一个ipmi
                Result result;
                if (rackHdNode.getObms().isEmpty()) {
                    logger.error("节点{}的obm设置不存在，手动创建...", info.getHost());
                    result = rackHdService.request(JSONObject.toJSONString(obmInfo),
                            rackHdService.buildUrl(EndPoints.NODE_OBM_V2, rackHdNode.getId()), BoCloudMethod.PUT);
                } else {
                    // 检查该节点obm的ref是否正确
                    Map<String, Object> obm = rackHdNode.getObms().get(0);
                    String ref = (String) obm.get(Constant.REF);
                    if (!StringUtils.isNotEmpty(ref)) {
                        logger.error("节点{}的obm设置有问题，请重新新增改机器", info.getHost());
                        return new BocloudResult(false, "更新ipmi信息失败");
                    } else {
                        // 更新rackhd的带外管理配置
                        result = rackHdService.request(JSONObject.toJSONString(obmInfo), rackHdService.buildUrl(ref),
                                BoCloudMethod.PATCH);
                    }
                }
                logger.info("设置物理机{}的ipmi信息[ip: {},user: {}], 结果{}", node.getName(), info.getHost(), info.getUser(),
                        result.toString());
                if (result.isFailed()) {
                    logger.error("请求rackhd更新物理机[{}]的ipmi信息失败:{}", node.getName(), result.getMessage());
                    return new BocloudResult(false, "请求rackhd更新物理机" + node.getName() + "的ipmi信息失败");
                }
                // 更新catalog里面ipmiUsers字段
                List<String> ipmiUsers = JSON.parseArray(node.getConfig().getIpmiUsers(), String.class);
                if (!ipmiUsers.contains(info.getUser())) {
                    ipmiUsers.add(info.getUser());
                    node.getConfig().setIpmiUsers(JSON.toJSONString(ipmiUsers));
                }
            }
            // 保存ipmi信息到指定节点
            node.getConfig().setIpmiAccount(info.getUser());
            node.getConfig().setIpmiAddress(info.getHost());
            node.getConfig().setIpmiPassword(cipherPassword);
            node.getConfig().setIpmiPort(info.getPort());
            node.setMenderId(context.getTarget());
            BocloudResult updatePServerResult = update(node, context);
            if (updatePServerResult.isFailed()) {
                logger.error("请求cmp更新物理机[{}]的ipmi信息失败:{}", node.getName(), updatePServerResult.getMessage());
                return new BocloudResult(false, "请求cmp更新物理机" + node.getName() + "的ipmi信息失败");
            }
            return new BocloudResult(true, "编辑物理机[" + node.getName() + "]的ipmi成功");
        } catch (Exception e) {
            logger.error("update ipmi info fail:", e);
            return new BocloudResult(false, "更新ipmi信息异常");
        }
    }

    @Override
    public BocloudResult cancel(Long nodeId, RequestContext context) {
        try {
            Server node = get(nodeId);
            if (node == null || node.getDeleted()) {
                logger.error("id为[{}]的物理机不存在，无法取消装机任务", nodeId);
                return new BocloudResult(false, "id为[" + nodeId + "]的物理机不存在");
            }
            List<InstallResult> installResults = this.installResultRepository.queryOn(nodeId);
            if (installResults.isEmpty()) {
                logger.error("当前物理机[" + nodeId + "]不存在装机中的任务");
                return new BocloudResult(false, "取消物理机[" + node.getName() + "]装机任务失败");
            }
            Map<String, Object> data = MapTools.simpleMap(Constant.COMMAND, Constant.CANCEL);
            Result cancelResult = rackHdService.request(JSON.toJSONString(data),
                    rackHdService.buildUrl(EndPoints.NODE_WORKFLOWS_ACTION_V2, node.getRackhdNodeId()),
                    BoCloudMethod.PUT);
            JSONObject message = JSONObject.parseObject(cancelResult.getMessage(), JSONObject.class);
            if (cancelResult.isSuccess() || Constant.HTTP_CODE_404.equals(message.getString(Constant.STATUS))) {
                // 更新数据库节点的装机状态
                node.setGmtModify(new Date());
                node.setMenderId(context.getTarget());
                node.setStatus(InstallStatus.INSTALL_CANCELED.getText());
                update(node, context);

                Map<String, Object> result = MapTools.simpleMap(Constant.ID, nodeId);
                result.put(Constant.STATUS, InstallStatus.INSTALL_CANCELED.getText());

                // 更新install_result
                for (InstallResult installResult : installResults) {
                    installResult.setStatus(InstallStatus.INSTALL_CANCELED.getText());
                    installResult.setResult(InstallStatus.INSTALL_CANCELED.getText());
                    installResult.setGmtFinish(new Date());
                    this.installResultRepository.update(installResult);
                }

                // 更新install_task
                List<Long> nodes = Lists.newArrayList();
                nodes.add(nodeId);
                if (this.installResultRepository.taskFinished(installResults.get(0).getTaskId(), nodes)) {
                    InstallTask installTask = this.installTaskRepository.query(installResults.get(0).getTaskId());
                    installTask.setStatus(InstallStatus.INSTALL_CANCELED.getText());
                    installTask.setGmtFinish(new Date());
                    this.installTaskRepository.update(installTask);
                }
                return new BocloudResult(true, result, "取消物理机[" + node.getName() + "]装机装机成功");
            } else {
                logger.error("从rackhd取消任务失败:{}", cancelResult.toString());
                return new BocloudResult(false, "从rackhd取消物理机[" + node.getName() + "]装机任务失败");
            }
        } catch (Exception e) {
            logger.error("取消装机任务失败:", e);
            return new BocloudResult(false, "取消装机任务失败");
        }
    }

    @Override
    public BocloudResult<Server> get(String params) {
        try {
            JSONObject object = JSONObject.parseObject(params, JSONObject.class);
            String rackhdNodeId = object.getString("rackhdNodeId");
            if (rackhdNodeId != null) {
                Server server = serverRepository.getByNodeId(rackhdNodeId);
                if (server == null) {
                    logger.warn("查询物理主机失败: params={}", params);
                    return new BocloudResult(false, "查询物理主机失败");
                }
                server.setConfig(serverConfigRepository.getBySid(server.getId()));
                server.setAssets(serverAssetsRepository.getBySid(server.getId()));
                return new BocloudResult(true, server, "查询物理主机成功");
            }
            String ipmiIp = object.getString("ipmiIp");
            if (ipmiIp != null) {
                Server server = getServerByIpmiIp(ipmiIp);
                if (server == null) {
                    logger.warn("查询物理主机失败: params={}", params);
                    return new BocloudResult(false, "查询物理主机失败");
                }
                return new BocloudResult(true, server, "查询物理主机成功");
            }
            logger.warn("查询物理主机失败: params={}", params);
            return new BocloudResult(false, "查询物理主机失败，参数" + params + "异常");
        } catch (Exception e) {
            logger.error("get server info failed:", e);
            return new BocloudResult(false, "查询物理主机异常");
        }
    }

    public Server get(Long id) {
        try {
            Server server = serverRepository.query(id);
            if (null == server) {
                logger.warn("server does not exist!");
                return null;
            }
            server.setNetcards(networkCardRepository.listByHost(id, "PM"));
            server.setRaids(serverRaidCtrlRepository.getBySid(server.getId()));
            ServerConfig config = serverConfigRepository.getBySid(id);
            ServerAssets assets = serverAssetsRepository.getBySid(id);
            server.setConfig(null == config ? new ServerConfig() : config);
            server.setAssets(null == assets ? new ServerAssets() : assets);
            if (null != server.getImageId()) {
                Image image = imageRepository.queryPhysical(server.getImageId());
                if (image != null) {
                    server.setImageName(image.getName());
                    server.setImageIsoName(image.getIsoName());
                }
            }
            String dhcpIp = catalogService.dhcpIp(server.getIdentifier(), true);
            if (dhcpIp != null && !dhcpIp.equals(server.getManageIp())) {
                server.setManageIp(dhcpIp);
                if (server.getNetcards() != null) {
                    for (NetworkCard netCard : server.getNetcards()) {
                        if (server.getIdentifier().equalsIgnoreCase(netCard.getMac())) {
                            netCard.setIpAddress(dhcpIp);
                            break;
                        }
                    }
                }
                serverRepository.update(server);
            }
            return server;
        } catch (Exception e) {
            logger.error("Query server detail fail:", e);
            return null;
        }
    }

    @Override
    public BocloudResult detail(Long id) {
        try {
            Server server = get(id);
            return new BocloudResult(true, server, "查询成功");
        } catch (Exception e) {
            logger.error("Query server detail fail:", e);
            return new BocloudResult(false, "查询失败");
        }
    }

    @Override
	public Server getServerByIpmiIp(String ipmiIp) {
        try {
            ServerConfig config = this.serverConfigRepository.getConfig(ipmiIp);
            if (null == config) {
                logger.error("ipmiIp为{}的物理机配置不存在", ipmiIp);
                return null;
            }
            Server server = this.serverRepository.query(config.getServerId());
            if (server == null) {
                logger.error("ipmiIp为{}的物理机不存在", ipmiIp);
                return null;
            }
            server.setConfig(config);
            server.setAssets(serverAssetsRepository.getBySid(config.getServerId()));
            return server;
        } catch (Exception e) {
            logger.error("Get server error:", e);
            return null;
        }
    }

    private BocloudResult check(Server node) {
        if (null == node) {
            return new BocloudResult(false, "物理机信息不能为空");
        }
        // license节点数校验
        BocloudResult licenseResult = serverService.licenseCheck(node.getVendorId(), 1, 0);
        if (licenseResult.isFailed()) {
            return licenseResult;
        }
        String name = node.getName();

        if (!StringUtils.isNotEmpty(name)) {
            return new BocloudResult(false, "物理机的名称不能为空");
        }
        ServerConfig config = node.getConfig();
        if (null == config || !StringUtils.isNotEmpty(config.getIpmiAddress())) {
            return new BocloudResult(false, "物理机的IPMI IP不能为空");
        }
        String ipmiIp = node.getConfig().getIpmiAddress();

        // [名称]不能与现有数据重复
        try {
            List<Server> servers = serverRepository.getByAssetCategory(ServerAssetCategory.BARE_METAL.name());
            for (Server server : servers) {
                if (name.equals(server.getName())) {
                    return new BocloudResult(false, "系统中已经存在[名称]为[" + name + "]的物理机，请输入其它名称。");
                }
            }
        } catch (Exception e) {
            return new BocloudResult(false, "检验物理机[名称]是否存在时出现错误：" + e.getMessage());
        }

        // [IPMI IP]不能与现有数据重复
        try {
            ServerConfig serverConfig = serverConfigRepository.getConfig(ipmiIp);
            if (serverConfig != null) {
                return new BocloudResult(false, "系统中已经存在[IPMI IP]为[" + ipmiIp + "]的物理机，请确认输入的[IPMI IP]是否正确。");
            }
        } catch (Exception e) {
            return new BocloudResult(false, "检验物理机[IPMI IP]是否存在时出现错误：" + e.getMessage());
        }
        return new BocloudResult(true, "检验通过");
    }

    private void savePci(ServerPci serverPci, Long serverId) {
        if (serverPci == null) {
            return;
        }
        if (StringUtils.isNotEmpty(serverPci.getBusInfo())) {
            serverPci.setServerId(serverId);
            serverPciRepository.save(serverPci);
        }
        if (serverPci.getChildren() != null) {
            for (ServerPci child : serverPci.getChildren()) {
                child.setParentId(serverPci.getId());
                savePci(child, serverId);
            }
        }
    }

    private Long getPciId(String mac, Long serverId) {
        try {
            ServerPci serverPci = serverPciRepository.get(mac, serverId);
            if (serverPci != null) {
                return serverPci.getId();
            }
        } catch (Exception ignored) {
        }
        return null;
    }

    @Override
    public BocloudResult save(Server server, RequestContext context) {
        try {
            // license节点数校验
            BocloudResult licenseResult = serverService.licenseCheck(server.getVendorId(), 1, 0);
            if (licenseResult.isFailed()) {
                return licenseResult;
            }
            server.preSave(context.getTarget(), context.getTarget(), null, null, null);
            this.serverRepository.save(server);
            ServerConfig config = server.getConfig();
            if (config != null) {
                config.setServerId(server.getId());
                this.serverConfigRepository.save(config);
            }
            ServerAssets assets = server.getAssets();
            if (assets != null) {
                assets.setServerId(server.getId());
                this.serverAssetsRepository.save(assets);
            }

            savePci(server.getServerPci(), server.getId());

            List<NetworkCard> networkCards = server.getNetcards();
            if (networkCards != null) {
                for (NetworkCard networkCard : networkCards) {
                    networkCard.setHostId(server.getId());
                    networkCard.setCategory("PM");
                    networkCard.setGmtCreate(new Date());
                    networkCard.setGmtModify(new Date());
                    networkCard.setCreatorId(context.getTarget());
                    networkCard.setMenderId(context.getTarget());
                    networkCard.setPciId(getPciId(networkCard.getMac(), server.getId()));
                    networkCardRepository.save(networkCard);
                }
            }

            if (server.getRaids() != null) {
                for (ServerRaidCtrl raid : server.getRaids()) {
                    ServerPci serverPci = serverPciRepository.get(server.getId(), raid.getPciAddress());
                    if (serverPci == null) {
                        logger.warn("fail to get server {}(id={}) pci info using raid pci {}", server.getName(),
                                server.getId(), raid.getPciAddress());
                    } else {
                        raid.setPciId(serverPci.getId());
                    }
                    raid.setServerId(server.getId());
                    serverRaidCtrlRepository.save(raid);
                    for (Mdisk mdisk : raid.getDiskGroups()) {
                        mdisk.setGmtCreate(new Date());
                        mdisk.setGmtModify(new Date());
                        mdisk.setCreatorId(context.getTarget());
                        mdisk.setMenderId(context.getTarget());
                        mdisk.setControllerId(raid.getId());
                        mdiskRepository.save(mdisk);
                        mdisk.getDisks().forEach(disk -> {
                            try {
                                disk.setGmtCreate(new Date());
                                disk.setGmtModify(new Date());
                                disk.setCreatorId(context.getTarget());
                                disk.setMenderId(context.getTarget());
                                disk.setControllerId(raid.getId());
                                disk.setMdiskId(mdisk.getId());
                                diskRepository.save(disk);
                            } catch (Exception ignored) {
                            }
                        });
                    }
                    raid.getDisks().forEach(disk -> {
                        if (!StringUtils.isNotEmpty(disk.getMdiskName())) {
                            try {
                                disk.setGmtCreate(new Date());
                                disk.setGmtModify(new Date());
                                disk.setCreatorId(context.getTarget());
                                disk.setMenderId(context.getTarget());
                                disk.setControllerId(raid.getId());
                                diskRepository.save(disk);
                            } catch (Exception ignored) {
                            }
                        }
                    });
                }
            }

            // 新增到resource表, osCategory设置为null
            Resource resource = new Resource(ResourceCategory.Computer.name(), ResourceCatalog.PHYSICAL.name(),
                    server.getId(), server.getNetcards() == null ? null :
                    JSONObject.toJSONString(server.getNetcards().stream().map(NetworkCard::getIpAddress)
                            .filter(Objects::nonNull).distinct().collect(Collectors.toList())),
                    null, null);
            if (config != null) {
                resource.setUsername(config.getUsername());
                resource.setPassword(config.getPassword());
            }
            resource.preSave(server.getName(), server.getStatus(), context.getTarget(), context.getTenant(),
                    context.getProject());
            resource.setInventory(JSONObject.toJSONString(server));
            resource.setStatus(server.getPowerState());
            resourceRepository.save(resource);

            // 同步到cmdb
            List<CmdbInfo> infos = Lists.newArrayList();
            infos.add(CmdbInfoConvertor.getInfo(server, config, null, null));
            cmdbMessagePublisher.send(CmdbInfoConvertor.getModel(BoCloudService.IMS, infos));
            return new BocloudResult(true, server, "保存物理机[" + server.getName() + "]信息成功");
        } catch (Exception e) {
            logger.error("Save server fail:", e);
            return new BocloudResult(false, "保存失败");
        }
    }

    private List<ServerPci> pciList(List<ServerPci> children, ServerPci parent) {
        if (children == null) {
            return new ArrayList<>();
        }
        List<ServerPci> records = new ArrayList<>();
        for (ServerPci child : children) {
            child.setParentId(parent == null ? null : parent.getId());
            records.add(child);
            if (child.getChildren() != null) {
                records.addAll(pciList(child.getChildren(), child));
            }
        }
        return records;
    }

    private void updatePci(ServerPci serverPci, Long serverId) throws Exception {
        if (serverPci == null) {
            return;
        }
        if (StringUtils.isNotEmpty(serverPci.getBusInfo())) {
            ServerPci dbPci = serverPciRepository.get(serverId, serverPci.getBusInfo());
            if (dbPci == null) {
                serverPci.setServerId(serverId);
                serverPciRepository.save(serverPci);
            } else {
                BeanUtils.copyProperties(serverPci, dbPci);
                serverPci.setServerId(serverId);
                serverPciRepository.updateEntity(dbPci);
            }
        }
        if (serverPci.getChildren() != null) {
            for (ServerPci child : serverPci.getChildren()) {
                child.setParentId(serverPci.getId());
                updatePci(child, serverId);
            }
        }
    }

    @Override
    public BocloudResult<String> update(Server server, RequestContext context) {
        String path = Server.class.getSimpleName() + "_" + server.getId();
        try (BocloudACLock lock = lockFactory.getACLock(path)) {
            Assert.isTrue(lock.acquire(10, TimeUnit.SECONDS), "请求更新物理机[" + server.getId() + "]分布式锁超时");
            Server host = this.serverRepository.query(server.getId());
            if (null == host) {
                return new BocloudResult(false, "id为[" + server.getId() + "]的物理机不存在");
            }
            ServerConfig config = this.serverConfigRepository.getBySid(server.getId());
            if (server.getConfig() != null) {
                if (config == null) {
                    config = server.getConfig();
                    config.setServerId(server.getId());
                    this.serverConfigRepository.save(config);
                } else {
                    config.setIpmiAddress(server.getConfig().getIpmiAddress());
                    config.setIpmiPort(server.getConfig().getIpmiPort());
                    config.setIpmiIps(server.getConfig().getIpmiIps());
                    config.setVendor(server.getConfig().getVendor());
                    config.setIpmiUsers(server.getConfig().getIpmiUsers());
                    config.setIpmiAccount(server.getConfig().getIpmiAccount());
                    config.setIpmiPassword(server.getConfig().getIpmiPassword());
                    config.setCpuVendor(server.getConfig().getCpuVendor());
                    config.setCpuAmount(server.getConfig().getCpuAmount());
                    config.setCpuMode(server.getConfig().getCpuMode());
                    config.setVirtualized(server.getConfig().getVirtualized());
                    config.setCpuHz(server.getConfig().getCpuHz());
                    config.setMemoryMode(server.getConfig().getMemoryMode());
                    config.setMemoryCapacity(server.getConfig().getMemoryCapacity());
                    config.setDiskCapacity(server.getConfig().getDiskCapacity());
                    config.setBiosVersion(server.getConfig().getBiosVersion());
                    config.setBiosVendor(server.getConfig().getBiosVendor());
                    config.setBiosDate(server.getConfig().getBiosDate());
                    config.setUsername(server.getConfig().getUsername());
                    config.setPassword(server.getConfig().getPassword());
                    this.serverConfigRepository.update(config);
                }
            }
            if (server.getAssets() != null) {
                ServerAssets assets = this.serverAssetsRepository.getBySid(server.getId());
                if (assets == null) {
                    assets = server.getAssets();
                    assets.setServerId(server.getId());
                    this.serverAssetsRepository.save(assets);
                } else {
                    assets.setSn(server.getAssets().getSn());
                    assets.setMaker(server.getAssets().getMaker());
                    this.serverAssetsRepository.update(assets);
                }
            }

            // 先更新server_pci，再更新network_card
            updatePci(server.getServerPci(), server.getId());
            List<ServerPci> dbPcis = serverPciRepository.getBySid(server.getId());
            if (dbPcis != null && server.getServerPci() != null) {
                List<ServerPci> pcis = pciList(server.getServerPci().getChildren(), server.getServerPci());
                for (ServerPci dbPci : dbPcis) {
                    boolean found = false;
                    for (ServerPci pci : pcis) {
                        if (dbPci.getBusInfo().equalsIgnoreCase(pci.getBusInfo())) {
                            found = true;
                            break;
                        }
                    }
                    if (!found) {
                        serverPciRepository.delete(dbPci);
                    }
                }
            }

            if (server.getNetcards() != null) {
                List<NetworkCard> networkCards = this.networkCardRepository.listByHost(server.getId(), "PM");
                if (networkCards == null) {
                    for (NetworkCard networkCard : server.getNetcards()) {
                        networkCard.setHostId(server.getId());
                        networkCard.setCategory("PM");
                        networkCard.setGmtCreate(new Date());
                        networkCard.setGmtModify(new Date());
                        networkCard.setCreatorId(context.getTarget());
                        networkCard.setMenderId(context.getTarget());
                        networkCard.setPciId(getPciId(networkCard.getMac(), server.getId()));
                        networkCardRepository.save(networkCard);
                    }
                } else {
                    // 遍历所有的新网卡，找到对应的数据库网卡，如果有，更新数据库，没有，则保存
                    for (NetworkCard newCard : server.getNetcards()) {
                        boolean exist = false;
                        for (NetworkCard card : networkCards) {
                            if (newCard != null && newCard.getMac() != null
                                    && newCard.getMac().equalsIgnoreCase(card.getMac())) {
                                card.setPortType(newCard.getPortType());
                                card.setNumber(newCard.getNumber());
                                card.setMtu(newCard.getMtu());
                                card.setMac(newCard.getMac());
                                card.setStatus(newCard.getStatus());
                                card.setIpAddress(newCard.getIpAddress());
                                card.setProps(newCard.getProps());
                                card.setMenderId(context.getTarget());
                                card.setGmtModify(new Date());
                                card.setPciId(getPciId(newCard.getMac(), server.getId()));
                                this.networkCardRepository.update(card);
                                exist = true;
                                break;
                            }
                        }
                        if (!exist) {
                            if (newCard != null) {
                                newCard.setHostId(server.getId());
                                newCard.setGmtCreate(new Date());
                                newCard.setGmtModify(new Date());
                                newCard.setCreatorId(context.getTarget());
                                newCard.setMenderId(context.getTarget());
                                newCard.setCategory("PM");
                                newCard.setPciId(getPciId(newCard.getMac(), server.getId()));
                                networkCardRepository.save(newCard);
                            }
                        }
                    }
                    // 遍历数据库的网卡，在新的网卡中查找是否有对应的项目，如果没有，则删除。
                    for (NetworkCard card : networkCards) {
                        boolean found = false;
                        for (NetworkCard newCard : server.getNetcards()) {
                            if (newCard != null && newCard.getMac() != null
                                    && newCard.getMac().equalsIgnoreCase(card.getMac())) {
                                found = true;
                                break;
                            }
                        }
                        if (!found) {
                            this.networkCardRepository.remove(card.getId(), context.getTarget());
                        }
                    }
                }
            }

            if (server.getRaids() != null) {
                for (ServerRaidCtrl raid : server.getRaids()) {
                    ServerPci serverPci = serverPciRepository.get(server.getId(), raid.getPciAddress());
                    ServerRaidCtrl dbRaid = serverRaidCtrlRepository.get(server.getId(), raid.getControllerId());
                    if (dbRaid == null) {
                        dbRaid = raid;
                    } else {
                        BeanUtils.copyProperties(raid, dbRaid, "id", "serverId");
                    }
                    dbRaid.setServerId(server.getId());
                    dbRaid.setPciId(serverPci.getId());
                    if (dbRaid.getId() == null) {
                        serverRaidCtrlRepository.save(dbRaid);
                    } else {
                        serverRaidCtrlRepository.updateEntity(dbRaid);
                    }
                    for (Mdisk diskGroup : raid.getDiskGroups()) {
                        Mdisk dbMdisk = mdiskRepository.getByControllerIdAndUuid(dbRaid.getId(), diskGroup.getMdiskUuid());
                        if (dbMdisk == null) {
                            dbMdisk = diskGroup;
                            dbMdisk.setGmtCreate(new Date());
                            dbMdisk.setCreatorId(context.getTarget());
                        } else {
                            BeanUtils.copyProperties(diskGroup, dbMdisk, "id", "controllerId");
                        }
                        dbMdisk.setGmtModify(new Date());
                        dbMdisk.setMenderId(context.getTarget());
                        dbMdisk.setControllerId(dbRaid.getId());
                        dbMdisk.setDeleted(false);
                        if (dbMdisk.getId() == null) {
                            mdiskRepository.save(dbMdisk);
                        } else {
                            mdiskRepository.update(dbMdisk);
                        }
                        for (Disk disk : diskGroup.getDisks()) {
                            Disk dbDisk = diskRepository.getbyControllerIdAndUuid(dbRaid.getId(), disk.getDiskUuid());
                            if (dbDisk == null) {
                                dbDisk = disk;
                                dbDisk.setGmtCreate(new Date());
                                dbDisk.setCreatorId(context.getTarget());
                            } else {
                                BeanUtils.copyProperties(disk, dbDisk, "id", "controllerId", "mdiskId", "deleted");
                            }
                            dbDisk.setGmtModify(new Date());
                            dbDisk.setMenderId(context.getTarget());
                            dbDisk.setMdiskId(dbMdisk.getId());
                            dbDisk.setControllerId(dbRaid.getId());
                            if (dbDisk.getId() == null) {
                                dbDisk.setCreatorId(context.getTarget());
                                diskRepository.save(dbDisk);
                            } else {
                                diskRepository.update(dbDisk);
                            }
                        }

                        List<Disk> dbDisks = diskRepository.listByMdisk(dbMdisk.getId());
                        if (dbDisks != null) {
                            for (Disk dbDisk : dbDisks) {
                                boolean found = false;
                                for (Disk disk : diskGroup.getDisks()) {
                                    if (dbDisk.getDiskUuid().equalsIgnoreCase(disk.getDiskUuid())) {
                                        found = true;
                                        break;
                                    }
                                }
                                if (!found) {
                                    diskRepository.remove(dbDisk.getId());
                                }
                            }
                        }
                    }
                    List<Mdisk> dbMdisks = mdiskRepository.getByControllerId(dbRaid.getId());
                    if (dbMdisks != null) {
                        for (Mdisk dbMdisk : dbMdisks) {
                            boolean found = false;
                            for (Mdisk mdisk : raid.getDiskGroups()) {
                                if (dbMdisk.getMdiskUuid().equalsIgnoreCase(mdisk.getMdiskUuid())) {
                                    found = true;
                                    break;
                                }
                            }
                            if (!found) {
                                mdiskRepository.remove(dbMdisk.getId());
                            }
                        }
                    }
                    for (Disk disk : raid.getDisks()) {
                        // 这里只需要处理不在mdisk中的磁盘
                        if (StringUtils.isNotEmpty(disk.getMdiskName())) {
                            continue;
                        }
                        Disk dbDisk = diskRepository.getbyControllerIdAndUuid(dbRaid.getId(), disk.getDiskUuid());
                        if (dbDisk == null) {
                            dbDisk = disk;
                            dbDisk.setGmtCreate(new Date());
                            dbDisk.setCreatorId(context.getTarget());
                        } else {
                            BeanUtils.copyProperties(disk, dbDisk, "id", "controllerId", "gmtCreate", "creatorId", "deleted");
                        }
                        dbDisk.setGmtModify(new Date());
                        dbDisk.setMenderId(context.getTarget());
                        dbDisk.setControllerId(dbRaid.getId());
                        if (dbDisk.getId() == null) {
                            diskRepository.save(dbDisk);
                        } else {
                            diskRepository.update(dbDisk);
                        }
                    }

                    List<Disk> dbDisks = diskRepository.getByControllerId(dbRaid.getId());
                    if (dbDisks != null) {
                        for (Disk dbDisk : dbDisks) {
                            boolean found = false;
                            for (Disk disk : raid.getDisks()) {
                                if (dbDisk.getDiskUuid().equalsIgnoreCase(disk.getDiskUuid())) {
                                    found = true;
                                    break;
                                }
                            }
                            if (!found) {
                                diskRepository.remove(dbDisk.getId());
                            }
                        }
                    }
                }
                List<ServerRaidCtrl> dbRaids = serverRaidCtrlRepository.getBySid(server.getId());
                if (dbRaids != null) {
                    for (ServerRaidCtrl dbRaid : dbRaids) {
                        boolean found = false;
                        for (ServerRaidCtrl raid : server.getRaids()) {
                            if (dbRaid.getPciAddress().equalsIgnoreCase(raid.getPciAddress())) {
                                found = true;
                                break;
                            }
                        }
                        if (!found) {
                            serverRaidCtrlRepository.remove(dbRaid.getId());
                        }
                    }
                }
            }
            server.setMenderId(context.getTarget());
            server.setGmtModify(new Date());
            this.serverRepository.update(server);

            Resource resource = this.resourceRepository.query(ResourceCategory.Computer.name(),
                    ResourceCatalog.PHYSICAL.name(), server.getId());
            if (null != resource) {
                resource.setName(server.getName());
                resource.setUsername(config.getUsername());
                resource.setPassword(config.getPassword());
                resource.setPrivateIps(server.getNetcards() == null ? null :
                        JSONObject.toJSONString(server.getNetcards().stream().map(NetworkCard::getIpAddress)
                                .filter(Objects::nonNull).distinct().collect(Collectors.toList())));
                resource.setStatus(server.getPowerState());
                resource.setOsCategory(server.getOsCategory());
                resource.setInventory(JSONObject.toJSONString(server));
                resourceRepository.update(resource);
            }
            // 同步到cmdb
            List<CmdbInfo> infos = Lists.newArrayList();
            infos.add(CmdbInfoConvertor.getInfo(server, config, "modify", null));
            cmdbMessagePublisher.send(CmdbInfoConvertor.getModel(BoCloudService.IMS, infos));
            return new BocloudResult(true, "修改物理机[" + server.getName() + "]成功");
        } catch (Exception e) {
            logger.error("Modify server fail:", e);
            return new BocloudResult(false, "修改失败");
        }
    }

    @Override
    public BocloudResult listIpmiUserName(Long id) {
        try {
            ServerConfig config = serverConfigRepository.getBySid(id);
            if (config == null) {
                return new BocloudResult(false, "查询失败，id为[" + id + "]的物理机配置信息不存在");
            }
            List<String> userNames = JSON.parseArray(config.getIpmiUsers(), String.class);
            // 组装返回数据
            List<Map<String, String>> beans = new ArrayList<>(userNames.size());
            Map<String, String> bean = Maps.newHashMap();
            for (String item : userNames) {
                bean.put("name", item);
                beans.add(bean);
            }
            return new BocloudResult(true, beans, "查询成功");
        } catch (Exception e) {
            logger.error("ListIpmiUserName failed:", e);
            return new BocloudResult(false, "查询失败");
        }
    }

    @Override
    public BocloudResult remove(Long id, Boolean force, RequestContext context) {
        try {
            Server node = this.serverRepository.query(id);
            if (null == node) {
                logger.warn("Server data does not exist!");
                return new BocloudResult(false, "主机不存在");
            }

            // 先删除RackHD中的数据
            String rackhdNodeId = node.getRackhdNodeId();
            if (!StringUtils.isEmpty(rackhdNodeId)) {
                // 删除node
                String nodeUrl = rackHdService.buildUrl(EndPoints.NODE_V2, rackhdNodeId);
                Result nodeResult = rackHdService.request("", nodeUrl, BoCloudMethod.DELETE);

                if (nodeResult.isFailed()) {
                    JSONObject object = JSONTools.isJSONObj(nodeResult.getMessage());
                    if (object == null) {
                        logger.error("删除物理机[{}]时，调用RackHD接口失败:{}", node.getName(), nodeResult);
                        return new BocloudResult(false, "删除物理机[" + node.getName() + "]时，调用RackHD接口失败");
                    } else {
                        if (!Constant.HTTP_CODE_404.equals(object.getString(Constant.STATUS))) {
                            logger.error("调用RackHD接口删除物理机[{}]失败:{}", node.getName(), nodeResult);
                            return new BocloudResult(false, "删除物理机[" + node.getName() + "]时，调用RackHD接口失败");
                        }
                    }
                }
            }

            installResultRepository.delete(id);
            // 获取主机raid卡
            List<ServerRaidCtrl> raids = serverRaidCtrlRepository.getBySid(id);
            raids.forEach(raid -> {
                diskRepository.removeByControllerId(raid.getId());
                mdiskRepository.removeByControllerId(raid.getId());
            });

            serverRaidCtrlRepository.removeBySid(id);
            serverPciRepository.removeBySid(id);
            serverRepository.remove(id, context.getTarget());
            serverConfigRepository.removeBySid(id);
            serverAssetsRepository.removeBySid(id);
            networkCardRepository.removeByHost(id, "VM", context.getTarget());
            resourceRepository.remove(id, ResourceCategory.Computer.name(), ResourceCatalog.PHYSICAL.name(), context.getTarget());
            // 同步到cmdb
            List<CmdbInfo> infos = Lists.newArrayList();
            infos.add(CmdbInfoConvertor.getInfo(node, null, "delete", null));
            cmdbMessagePublisher.send(CmdbInfoConvertor.getModel(BoCloudService.IMS, infos));
            return new BocloudResult(true, id, "删除成功");
        } catch (Exception e) {
            logger.error("remove failed:", e);
            return new BocloudResult(false, "删除失败");
        }
    }

    /**
     * 分页显示物理机操作历史
     *
     * @author sxp
     * @since 2017-11-14
     */
    @Override
    public BocloudResult history(int page, int rows, List<Param> params, Map<String, String> sorter, Boolean simple) {
        List<InstallResult> list;
        List<SimpleBean> beans;
        GridBean gridBean;
        try {
            if (sorter == null) {
                sorter = new HashMap<>(1);
                sorter.put("gmtStart", "1");
            }
            if (simple) {
                beans = this.installResultRepository.list(params, sorter);
                gridBean = new GridBean(1, 1, beans.size(), beans);
            } else {
                int total = this.installResultRepository.count(params);
                list = this.installResultRepository.list(page, rows, params, sorter);
                processResult(list);
                gridBean = GridHelper.getBean(page, rows, total, list);
            }
            return new BocloudResult(true, gridBean, "查询成功");
        } catch (Exception e) {
            logger.error("Query node result list fail:", e);
            return new BocloudResult(false, "查询失败", null, null);
        }
    }

    /**
     * 对List<InstallResult>增加额外的属性
     * <p>
     * 增加总耗时。
     * </p>
     *
     * @author sxp
     * @since Nov 14, 2017
     */
    private void processResult(List<InstallResult> list) {
        if (list == null || list.isEmpty()) {
            return;
        }

        Date gmtStart, gmtFinish;
        String elapsedTime;

        for (InstallResult item : list) {
            if (TaskType.OSINSTALL.name().equals(item.getTaskType())) {
                item.setTypeName("操作系统安装");
            } else if (TaskType.RAID.name().equals(item.getTaskType())) {
                item.setTypeName("RAID配置");
            }
            gmtStart = item.getGmtStart();
            gmtFinish = item.getGmtFinish();

            // 总耗时: gmtFinish - gmtStart，然后再处理一下时间单位
            if (gmtStart != null && gmtFinish != null) {
                elapsedTime = DateUtils.getElapsedTimeWith4Digits(gmtStart, gmtFinish);
            } else {
                elapsedTime = "";
            }
            item.setElapsedTime(elapsedTime);
        }
    }

    /**
     * 查询物理机的IPMI用户名
     *
     * @param page   当前页码
     * @param rows   页面数据大小
     * @param params 查询参数 [{"param":{"id": 11},"sign":"EQ"}]
     * @author sxp
     * @since Nov 21, 2017
     */
    @Override
    public BocloudResult listIpmiUserName(int page, int rows, List<Param> params, Map<String, String> sorter,
                                          Boolean simple) {
        try {
            Map<String, Object> paramMap = QueryBuilder.getParam(params);
            Object obj = paramMap.get(Common.ID);
            Long id;

            if (obj instanceof Long) {
                id = (Long) obj;
            } else {
                try {
                    id = new Long(String.valueOf(obj));
                } catch (Exception e) {
                    return new BocloudResult(false, "查询失败，传入的参数id不是数字");
                }
            }

            if (id == 0) {
                return new BocloudResult(false, "查询失败，传入的参数id不能为空，不能为0");
            }

            Server node = get(id);
            if (null == node) {
                return new BocloudResult(false, "查询失败，id为[" + id + "]的物理机不存在");
            }
            if (node.getConfig().getIpmiUsers() == null) {
                return new BocloudResult(true, new GridBean(1, 1, 0, new ArrayList<>()), "查询成功");
            }
            List<String> userNames = JSON.parseArray(node.getConfig().getIpmiUsers(), String.class);
            if (userNames == null) {
                return new BocloudResult(true, new GridBean(1, 1, 0, new ArrayList<>()), "查询成功");
            }

            // 组装返回数据
            List<Map<String, String>> beans = new ArrayList<>(userNames.size());
            Map<String, String> bean;
            for (String item : userNames) {
                bean = new HashMap<>(1);
                bean.put("name", item);
                beans.add(bean);
            }
            GridBean gridBean = new GridBean(1, 1, beans.size(), beans);

            return new BocloudResult(true, gridBean, "查询成功");
        } catch (Exception e) {
            logger.error("List Ipmi User Name failed:", e);
            return new BocloudResult(false, "查询失败", null, null);
        }
    }

    @Override
    public BocloudResult console(Long nodeId, RequestContext context) {
        try {
            Server node = get(nodeId);
            if (node == null || node.getDeleted()) {
                logger.error("id为[{}]的物理机不存在，无法打开控制台", nodeId);
                return new BocloudResult(false, "id为[" + nodeId + "]的物理机不存在");
            }
            if (!StringUtils.isNotEmpty(node.getConfig().getIpmiAddress())
                    || !StringUtils.isNotEmpty(node.getConfig().getIpmiAccount())
                    || !StringUtils.isNotEmpty(node.getConfig().getIpmiPassword())) {
                return new BocloudResult(false, "物理机[" + node.getName() + "]ipmi信息异常，请编辑ipmi信息");
            }
            String cookie;
            String content;
            String manufacturer = node.getConfig().getVendor();
            if (manufacturer != null) {
                if (manufacturer.toUpperCase().contains(LANGCHAO.toUpperCase())
                        || manufacturer.toUpperCase().contains(INSPUR.toUpperCase())) {
                    IpmiInfo info = new IpmiInfo(
                            node.getConfig().getIpmiAddress(),
                            node.getConfig().getIpmiAccount(),
                            node.getConfig().getIpmiPassword(),
                            node.getConfig().getIpmiPort()
                    );
                    cookie = IpmiRemoteViewer.getCookie(info);
                    if (cookie == null) {
                        return new BocloudResult(false, "获取物理机[" + node.getName() + "]ipmi console的cookie失败");
                    }
                    String ipmiPath = Constant.HTTP_PREFIX + node.getConfig().getIpmiAddress() + "/Java/jviewer.jnlp";
                    content = getContent(ipmiPath, cookie);
                } else if (manufacturer.toUpperCase().contains(HUAWEI.toUpperCase())) {
                    content = getContent(
                            node.getConfig().getIpmiAddress(),
                            node.getConfig().getIpmiAccount(),
                            new AESEncrptor().decrypt(node.getConfig().getIpmiPassword(), null)
                    );
                } else {
                    logger.warn("暂不支持打开{}厂商物理机[{}]的控制台", manufacturer, node.getName());
                    return new BocloudResult(false, "暂不支持打开[" + manufacturer + "]物理机[" + node.getName() + "]控制台");
                }
            } else {
                logger.warn("暂未获取到物理机{}产商信息", node.getName());
                return new BocloudResult(false, "暂未获取到物理机[" + node.getName() + "]的厂商信息");
            }
            if (content == null || content.isEmpty()) {
                return new BocloudResult(false, "获取机器[" + node.getName() + "]控制台内容失败");
            }

            Map<String, Object> result = MapTools.simpleMap(Constant.IPMI_IP, node.getConfig().getIpmiAddress());
            result.put("content", content);
            return new BocloudResult(true, result, "加载成功");
        } catch (Exception e) {
            logger.error("获取机器console信息失败:", e);
            return new BocloudResult(false, "获取机器console信息失败");
        }
    }

    private String getContent(String ipmiPath, String cookie) {
        logger.info("Start to get console content, ipmiPath={},cookie={}", ipmiPath, cookie);
        // get jnlp file content
        StringBuilder sb = new StringBuilder();
        try {
            URL newUrl = new URL(ipmiPath);
            if (ipmiPath.startsWith(HTTPS)) {
                SSLContext sslcontext = SSLContext.getInstance("SSL", "SunJSSE");
                sslcontext.init(null, new javax.net.ssl.TrustManager[]{new TrustManager()},
                        new java.security.SecureRandom());

                HostnameVerifier ignoreHostnameVerifier = (s, sslSession) -> true;
                HttpsURLConnection.setDefaultHostnameVerifier(ignoreHostnameVerifier);
                HttpsURLConnection.setDefaultSSLSocketFactory(sslcontext.getSocketFactory());
            }
            HttpURLConnection connection = (HttpURLConnection) newUrl.openConnection();
            connection.setRequestMethod("GET");
            connection.setRequestProperty("User-Agent",
                    "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.104 Safari/537.36");
            // 必须设置false，否则会自动redirect到重定向后的地址
            // connection.setInstanceFollowRedirects(false);
            connection.setInstanceFollowRedirects(true);
            if (StringUtils.isNotEmpty(cookie)) {
                connection.setRequestProperty("Cookie", "SessionCookie=" + cookie);
            }
            connection.connect();
            if (connection.getResponseCode() == HTTP_OK) {
                BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
                String line;
                while ((line = br.readLine()) != null) {
                    // 循环读取流
                    sb.append(line);
                }
                // 关闭流
                br.close();
                // 断开连接
                connection.disconnect();
                logger.info("console content={}", sb.toString());
                return sb.toString();
            } else {
                logger.error("开启console失败,code={},message={}", connection.getResponseCode(),
                        connection.getResponseMessage());
                // 断开连接
                connection.disconnect();
                return null;
            }
        } catch (Exception e) {
            logger.error("请求http控制台文件内容异常:", e);
            return null;
        }
    }
}
