// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.
//
// Automatically generated by addcopyright.py at 01/29/2013
// Apache License, Version 2.0 (the "License"); you may not use this
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// Automatically generated by addcopyright.py at 04/03/2012
package com.cloud.baremetal.networkservice;

import com.cloud.agent.IAgentControl;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.CheckNetworkAnswer;
import com.cloud.agent.api.CheckNetworkCommand;
import com.cloud.agent.api.CheckVirtualMachineAnswer;
import com.cloud.agent.api.CheckVirtualMachineCommand;
import com.cloud.agent.api.Command;
import com.cloud.agent.api.HostVmStateReportEntry;
import com.cloud.agent.api.MaintainAnswer;
import com.cloud.agent.api.MaintainCommand;
import com.cloud.agent.api.MigrateAnswer;
import com.cloud.agent.api.MigrateCommand;
import com.cloud.agent.api.PingCommand;
import com.cloud.agent.api.PingRoutingCommand;
import com.cloud.agent.api.PingRoutingWithNwGroupsCommand;
import com.cloud.agent.api.PrepareForMigrationAnswer;
import com.cloud.agent.api.PrepareForMigrationCommand;
import com.cloud.agent.api.ReadyAnswer;
import com.cloud.agent.api.ReadyCommand;
import com.cloud.agent.api.RebootAnswer;
import com.cloud.agent.api.RebootCommand;
import com.cloud.agent.api.SecurityGroupRulesCmd;
import com.cloud.agent.api.StartAnswer;
import com.cloud.agent.api.StartCommand;
import com.cloud.agent.api.StartupCommand;
import com.cloud.agent.api.StartupRoutingCommand;
import com.cloud.agent.api.StopAnswer;
import com.cloud.agent.api.StopCommand;
import com.cloud.agent.api.baremetal.IpmISetBootDevCommand;
import com.cloud.agent.api.baremetal.IpmISetBootDevCommand.BootDev;
import com.cloud.agent.api.baremetal.IpmiBootorResetCommand;
import com.cloud.agent.api.to.VirtualMachineTO;
import com.cloud.baremetal.manager.BaremetalManager;
import com.cloud.configuration.Config;
import com.cloud.host.Host.Type;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.resource.ServerResource;
import com.cloud.utils.Pair;
import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.script.OutputInterpreter;
import com.cloud.utils.script.Script;
import com.cloud.utils.script.Script2;
import com.cloud.utils.script.Script2.ParamType;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachine.PowerState;
import com.cloud.vm.dao.VMInstanceDao;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.log4j.Logger;

import javax.ejb.Local;
import javax.naming.ConfigurationException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Local(value = ServerResource.class)
public class BareMetalResourceBase extends ManagerBase implements ServerResource {
    private static final Logger s_logger = Logger.getLogger(BareMetalResourceBase.class);
    protected String _name;
    protected String _uuid;
    protected String _zone;
    protected String _pod;
    protected Long hostId;
    protected String _cluster;
    protected long _memCapacity;
    protected long _cpuCapacity;
    protected long _cpuNum;
    protected String _mac;
    protected String _username;
    protected String _password;
    protected String _ip;
    protected boolean _isEchoScAgent;
    protected IAgentControl _agentControl;
    protected Script2 _pingCommand;
    protected Script2 _setPxeBootCommand;
    protected Script2 _setDiskBootCommand;
    protected Script2 _rebootCommand;
    protected Script2 _getStatusCommand;
    protected Script2 _powerOnCommand;
    protected Script2 _powerOffCommand;
    protected Script2 _forcePowerOffCommand;
    protected Script2 _bootOrRebootCommand;
    protected String _vmName;
    protected int ipmiRetryTimes = 5;

    protected ConfigurationDao configDao;
    protected VMInstanceDao vmDao;


    @Override
    public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
        _name = name;
        _uuid = (String) params.get("guid");
        try {
            _memCapacity = Long.parseLong((String) params.get(ApiConstants.MEMORY)) * 1024L * 1024L;
            _cpuCapacity = Long.parseLong((String) params.get(ApiConstants.CPU_SPEED));
            _cpuNum = Long.parseLong((String) params.get(ApiConstants.CPU_NUMBER));
        } catch (NumberFormatException e) {
            throw new ConfigurationException(String.format("Unable to parse number of CPU or memory capacity "
                    + "or cpu capacity(cpu number = %1$s memCapacity=%2$s, cpuCapacity=%3$s", params.get(ApiConstants.CPU_NUMBER),
                    params.get(ApiConstants.MEMORY), params.get(ApiConstants.CPU_SPEED)));
        }

        _zone = (String) params.get("zone");
        _pod = (String) params.get("pod");
        _cluster = (String) params.get("cluster");
        hostId = (Long) params.get("hostId");
        _ip = (String) params.get(ApiConstants.PRIVATE_IP);
        _mac = (String) params.get(ApiConstants.HOST_MAC);
        _username = (String) params.get(ApiConstants.USERNAME);
        _password = (String) params.get(ApiConstants.PASSWORD);
        _vmName = (String) params.get("vmName");
        String echoScAgent = (String) params.get(BaremetalManager.EchoSecurityGroupAgent);
        vmDao = (VMInstanceDao) params.get("vmDao");
        configDao = (ConfigurationDao) params.get("configDao");

        if (_pod == null) {
            throw new ConfigurationException("Unable to get the pod");
        }

        if (_cluster == null) {
            throw new ConfigurationException("Unable to get the pod");
        }

        if (_ip == null) {
            throw new ConfigurationException("Unable to get the host address");
        }

        if (_mac.equalsIgnoreCase("unknown")) {
            throw new ConfigurationException("Unable to get the host mac address");
        }

        if (_mac.split(":").length != 6) {
            throw new ConfigurationException("Wrong MAC format(" + _mac
                    + "). It must be in format of for example 00:11:ba:33:aa:dd which is not case sensitive");
        }

        if (_uuid == null) {
            throw new ConfigurationException("Unable to get the uuid");
        }

        if (echoScAgent != null) {
            _isEchoScAgent = Boolean.valueOf(echoScAgent);
        }

        String ipmiIface = "default";
        try {
            ipmiIface = configDao.getValue(Config.BaremetalIpmiLanInterface.key());
        } catch (Exception e) {
            s_logger.debug(e.getMessage(), e);
        }

        try {
            ipmiRetryTimes = Integer.valueOf(configDao.getValue(Config.BaremetalIpmiRetryTimes.key()));
        } catch (Exception e) {
            s_logger.debug(e.getMessage(), e);
        }

        String injectScript = "scripts/util/ipmi.py";
        String scriptPath = Script.findScript("", injectScript);
        if (scriptPath == null) {
            throw new ConfigurationException("Cannot find ping script " + scriptPath);
        }
        String pythonPath = "/usr/bin/python";
        _pingCommand = new Script2(pythonPath, s_logger);
        _pingCommand.add(scriptPath);
        _pingCommand.add("ping");
        _pingCommand.add("interface=" + ipmiIface);
        _pingCommand.add("hostname=" + _ip);
        _pingCommand.add("usrname=" + _username);
        _pingCommand.add("password=" + _password, ParamType.PASSWORD);

        _setPxeBootCommand = new Script2(pythonPath, s_logger);
        _setPxeBootCommand.add(scriptPath);
        _setPxeBootCommand.add("boot_dev");
        _setPxeBootCommand.add("interface=" + ipmiIface);
        _setPxeBootCommand.add("hostname=" + _ip);
        _setPxeBootCommand.add("usrname=" + _username);
        _setPxeBootCommand.add("password=" + _password, ParamType.PASSWORD);
        _setPxeBootCommand.add("dev=pxe");

        _setDiskBootCommand = new Script2(pythonPath, s_logger);
        _setDiskBootCommand.add(scriptPath);
        _setDiskBootCommand.add("boot_dev");
        _setDiskBootCommand.add("interface=" + ipmiIface);
        _setDiskBootCommand.add("hostname=" + _ip);
        _setDiskBootCommand.add("usrname=" + _username);
        _setDiskBootCommand.add("password=" + _password, ParamType.PASSWORD);
        _setDiskBootCommand.add("dev=disk");

        _rebootCommand = new Script2(pythonPath, s_logger);
        _rebootCommand.add(scriptPath);
        _rebootCommand.add("reboot");
        _rebootCommand.add("interface=" + ipmiIface);
        _rebootCommand.add("hostname=" + _ip);
        _rebootCommand.add("usrname=" + _username);
        _rebootCommand.add("password=" + _password, ParamType.PASSWORD);

        _getStatusCommand = new Script2(pythonPath, s_logger);
        _getStatusCommand.add(scriptPath);
        _getStatusCommand.add("ping");
        _getStatusCommand.add("interface=" + ipmiIface);
        _getStatusCommand.add("hostname=" + _ip);
        _getStatusCommand.add("usrname=" + _username);
        _getStatusCommand.add("password=" + _password, ParamType.PASSWORD);

        _powerOnCommand = new Script2(pythonPath, s_logger);
        _powerOnCommand.add(scriptPath);
        _powerOnCommand.add("power");
        _powerOnCommand.add("interface=" + ipmiIface);
        _powerOnCommand.add("hostname=" + _ip);
        _powerOnCommand.add("usrname=" + _username);
        _powerOnCommand.add("password=" + _password, ParamType.PASSWORD);
        _powerOnCommand.add("action=on");

        _powerOffCommand = new Script2(pythonPath, s_logger);
        _powerOffCommand.add(scriptPath);
        _powerOffCommand.add("power");
        _powerOffCommand.add("interface=" + ipmiIface);
        _powerOffCommand.add("hostname=" + _ip);
        _powerOffCommand.add("usrname=" + _username);
        _powerOffCommand.add("password=" + _password, ParamType.PASSWORD);
        _powerOffCommand.add("action=soft");

        _forcePowerOffCommand = new Script2(pythonPath, s_logger);
        _forcePowerOffCommand.add(scriptPath);
        _forcePowerOffCommand.add("power");
        _forcePowerOffCommand.add("interface=" + ipmiIface);
        _forcePowerOffCommand.add("hostname=" + _ip);
        _forcePowerOffCommand.add("usrname=" + _username);
        _forcePowerOffCommand.add("password=" + _password, ParamType.PASSWORD);
        _forcePowerOffCommand.add("action=off");

        _bootOrRebootCommand = new Script2(pythonPath, s_logger);
        _bootOrRebootCommand.add(scriptPath);
        _bootOrRebootCommand.add("boot_or_reboot");
        _bootOrRebootCommand.add("interface=" + ipmiIface);
        _bootOrRebootCommand.add("hostname=" + _ip);
        _bootOrRebootCommand.add("usrname=" + _username);
        _bootOrRebootCommand.add("password=" + _password, ParamType.PASSWORD);

        return true;
    }

    protected boolean doScript(Script cmd) {
        return doScript(cmd, null);
    }

    protected boolean doScript(Script cmd, int retry) {
        return doScript(cmd, null, retry);
    }

    protected boolean doScript(Script cmd, OutputInterpreter interpreter) {
        return doScript(cmd, interpreter, ipmiRetryTimes);
    }

    protected boolean doScript(Script cmd, OutputInterpreter interpreter, int retry) {
        String res = null;
        while (retry-- > 0) {
            if (interpreter == null) {
                res = cmd.execute();
            } else {
                res = cmd.execute(interpreter);
            }
            if (res != null && res.startsWith("Error: Unable to establish LAN")) {
                s_logger.warn("IPMI script timeout(" + cmd.toString() + "), will retry " + retry + " times");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                }
                continue;
            } else if (res == null) {
                return true;
            } else {
                break;
            }
        }

        s_logger.warn("IPMI Scirpt failed due to " + res + "(" + cmd.toString() + ")");
        return false;
    }

    @Override
    public boolean start() {
        return true;
    }

    @Override
    public boolean stop() {
        return true;
    }

    @Override
    public String getName() {
        return _name;
    }

    @Override
    public Type getType() {
        return com.cloud.host.Host.Type.Routing;
    }

    protected Map<String, HostVmStateReportEntry> getHostVmStateReport() {
        Map<String, HostVmStateReportEntry> states = new HashMap<String, HostVmStateReportEntry>();
        if (hostId != null) {
            final List<? extends VMInstanceVO> vms = vmDao.listByHostId(hostId);
            for (VMInstanceVO vm : vms) {
                states.put(
                    vm.getInstanceName(),
                    new HostVmStateReportEntry(
                        vm.getPowerState(), "host-" + hostId
                    )
                );
            }
        }
        return states;
    }

    @Override
    public StartupCommand[] initialize() {
        StartupRoutingCommand cmd = new StartupRoutingCommand(0, 0, 0, 0, null, Hypervisor.HypervisorType.BareMetal,
            new HashMap<String, String>());

        cmd.setDataCenter(_zone);
        cmd.setPod(_pod);
        cmd.setCluster(_cluster);
        cmd.setGuid(_uuid);
        cmd.setName(_ip);
        cmd.setPrivateIpAddress(_ip);
        cmd.setStorageIpAddress(_ip);
        cmd.setVersion(BareMetalResourceBase.class.getPackage().getImplementationVersion());
        cmd.setCpus((int) _cpuNum);
        cmd.setSpeed(_cpuCapacity);
        cmd.setMemory(_memCapacity);
        cmd.setPrivateMacAddress(_mac);
        cmd.setPublicMacAddress(_mac);
        return new StartupCommand[] { cmd };
    }

    private boolean ipmiPing() {
        return doScript(_pingCommand);
    }

    @Override
    public PingCommand getCurrentStatus(long id) {
        try {
            if (!ipmiPing()) {
                Thread.sleep(1000);
                if (!ipmiPing()) {
                    s_logger.warn("Cannot ping ipmi nic " + _ip);
                    return null;
                }
            }
        } catch (Exception e) {
            s_logger.debug("Cannot ping ipmi nic " + _ip, e);
            return null;
        }

        if (hostId != null) {
            final List<? extends VMInstanceVO> vms = vmDao.listByHostId(hostId);
            if (vms.isEmpty()) {
                return new PingRoutingCommand(getType(), id, getHostVmStateReport());
            } else {
                VMInstanceVO vm = vms.get(0);
                SecurityGroupHttpClient client = new SecurityGroupHttpClient();
                HashMap<String, Pair<Long, Long>> nwGrpStates = client.sync(vm.getInstanceName(), vm.getId(), vm.getPrivateIpAddress());
                return new PingRoutingWithNwGroupsCommand(getType(), id, getHostVmStateReport(), nwGrpStates);
            }
        } else {
            return new PingRoutingCommand(getType(), id, getHostVmStateReport());
        }
    }

    protected Answer execute(IpmISetBootDevCommand cmd) {
        Script bootCmd = null;
        if (cmd.getBootDev() == BootDev.disk) {
            bootCmd = _setDiskBootCommand;
        } else if (cmd.getBootDev() == BootDev.pxe) {
            bootCmd = _setPxeBootCommand;
        } else {
            throw new CloudRuntimeException("Unkonwn boot dev " + cmd.getBootDev());
        }

        String bootDev = cmd.getBootDev().name();
        if (!doScript(bootCmd)) {
            s_logger.warn("Set " + _ip + " boot dev to " + bootDev + "failed");
            return new Answer(cmd, false, "Set " + _ip + " boot dev to " + bootDev + "failed");
        }

        s_logger.warn("Set " + _ip + " boot dev to " + bootDev + "Success");
        return new Answer(cmd, true, "Set " + _ip + " boot dev to " + bootDev + "Success");
    }

    protected MaintainAnswer execute(MaintainCommand cmd) {
        return new MaintainAnswer(cmd, false);
    }

    protected PrepareForMigrationAnswer execute(PrepareForMigrationCommand cmd) {
        return new PrepareForMigrationAnswer(cmd);
    }

    protected MigrateAnswer execute(MigrateCommand cmd) {
        if (!doScript(_powerOffCommand)) {
            return new MigrateAnswer(cmd, false, "IPMI power off failed", null);
        }
        return new MigrateAnswer(cmd, true, "success", null);
    }

    protected CheckVirtualMachineAnswer execute(final CheckVirtualMachineCommand cmd) {
        return new CheckVirtualMachineAnswer(cmd, PowerState.PowerOff, null);
    }

    protected Answer execute(IpmiBootorResetCommand cmd) {
        if (!doScript(_bootOrRebootCommand)) {
            return new Answer(cmd, false, "IPMI boot or reboot failed");
        }
        return new Answer(cmd, true, "Success");

    }

    protected CheckNetworkAnswer execute(CheckNetworkCommand cmd) {
        return new CheckNetworkAnswer(cmd, true, "Success");
    }

    protected Answer execute(SecurityGroupRulesCmd cmd) {
        SecurityGroupHttpClient hc = new SecurityGroupHttpClient();
        return hc.call(cmd.getGuestIp(), cmd);
    }

    @Override
    public Answer executeRequest(Command cmd) {
        try {
            if (cmd instanceof ReadyCommand) {
                return execute((ReadyCommand) cmd);
            } else if (cmd instanceof StartCommand) {
                return execute((StartCommand) cmd);
            } else if (cmd instanceof StopCommand) {
                return execute((StopCommand) cmd);
            } else if (cmd instanceof RebootCommand) {
                return execute((RebootCommand) cmd);
            } else if (cmd instanceof IpmISetBootDevCommand) {
                return execute((IpmISetBootDevCommand) cmd);
            } else if (cmd instanceof MaintainCommand) {
                return execute((MaintainCommand) cmd);
            } else if (cmd instanceof PrepareForMigrationCommand) {
                return execute((PrepareForMigrationCommand) cmd);
            } else if (cmd instanceof MigrateCommand) {
                return execute((MigrateCommand) cmd);
            } else if (cmd instanceof CheckVirtualMachineCommand) {
                return execute((CheckVirtualMachineCommand) cmd);
            } else if (cmd instanceof IpmiBootorResetCommand) {
                return execute((IpmiBootorResetCommand) cmd);
            } else if (cmd instanceof SecurityGroupRulesCmd) {
                return execute((SecurityGroupRulesCmd) cmd);
            } else if (cmd instanceof CheckNetworkCommand) {
                return execute((CheckNetworkCommand) cmd);
            } else {
                return Answer.createUnsupportedCommandAnswer(cmd);
            }
        } catch (Throwable t) {
            s_logger.debug(t.getMessage(), t);
            return new Answer(cmd, false, t.getMessage());
        }
    }

    protected boolean isPowerOn(String str) {
        if (str.startsWith("Chassis Power is on")) {
            return true;
        } else if (str.startsWith("Chassis Power is off")) {
            return false;
        } else {
            throw new CloudRuntimeException("Cannot parse IPMI power status " + str);
        }
    }

    protected RebootAnswer execute(final RebootCommand cmd) {
        String infoStr = "Command not supported in present state";
        OutputInterpreter.AllLinesParser interpreter = new OutputInterpreter.AllLinesParser();
        if (!doScript(_rebootCommand, interpreter, 10)) {
            if (interpreter.getLines().contains(infoStr)) {
                // try again, this error should be temporary
                if (!doScript(_rebootCommand, interpreter, 10)) {
                    return new RebootAnswer(cmd, "IPMI reboot failed", false);
                }
            } else {
                return new RebootAnswer(cmd, "IPMI reboot failed", false);
            }
        }

        return new RebootAnswer(cmd, "reboot succeeded", true);
    }

    protected StopAnswer execute(final StopCommand cmd) {
        boolean success = false;
        int count = 0;
        Script powerOff = _powerOffCommand;

        while (count < 10) {
            if (!doScript(powerOff)) {
                break;
            }

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                break;
            }

            OutputInterpreter.AllLinesParser interpreter = new OutputInterpreter.AllLinesParser();
            if (!doScript(_getStatusCommand, interpreter)) {
                success = true;
                s_logger.warn("Cannot get power status of " + _name + ", assume VM state changed successfully");
                break;
            }

            if (!isPowerOn(interpreter.getLines())) {
                success = true;
                break;
            } else {
                powerOff = _forcePowerOffCommand;
            }

            count++;
        }

        return success ? new StopAnswer(cmd, "Success", true) : new StopAnswer(cmd, "IPMI power off failed", false);
    }

    protected StartAnswer execute(StartCommand cmd) {
        VirtualMachineTO vm = cmd.getVirtualMachine();

        OutputInterpreter.AllLinesParser interpreter = new OutputInterpreter.AllLinesParser();
        if (!doScript(_getStatusCommand, interpreter)) {
            return new StartAnswer(cmd, "Cannot get current power status of " + _name);
        }

        if (isPowerOn(interpreter.getLines())) {
            if (!doScript(_rebootCommand)) {
                return new StartAnswer(cmd, "IPMI reboot failed");
            }
        } else {
            if (!doScript(_powerOnCommand)) {
                return new StartAnswer(cmd, "IPMI power on failed");
            }
        }

        if (_isEchoScAgent) {
            SecurityGroupHttpClient hc = new SecurityGroupHttpClient();
            boolean echoRet = hc.echo(vm.getNics()[0].getIp(), TimeUnit.MINUTES.toMillis(30), TimeUnit.MINUTES.toMillis(1));
            if (!echoRet) {
                return new StartAnswer(cmd, String.format("Call security group agent on vm[%s] timeout", vm.getNics()[0].getIp()));
            }
        }

        s_logger.debug("Start bare metal vm " + vm.getName() + "successfully");
        _vmName = vm.getName();
        return new StartAnswer(cmd);
    }

    protected ReadyAnswer execute(ReadyCommand cmd) {
        // derived resource should check if the PXE server is ready
        s_logger.debug("Bare metal resource " + _name + " is ready");
        return new ReadyAnswer(cmd);
    }

    @Override
    public void disconnected() {

    }

    @Override
    public IAgentControl getAgentControl() {
        return _agentControl;
    }

    @Override
    public void setAgentControl(IAgentControl agentControl) {
        _agentControl = agentControl;
    }

}
