| // 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. |
| package com.cloud.baremetal; |
| |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import javax.ejb.Local; |
| import javax.naming.ConfigurationException; |
| |
| import org.apache.log4j.Logger; |
| |
| 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.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.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.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 org.apache.cloudstack.api.ApiConstants; |
| import com.cloud.host.Host.Type; |
| import com.cloud.hypervisor.Hypervisor; |
| import com.cloud.resource.ServerResource; |
| 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.VirtualMachine; |
| import com.cloud.vm.VirtualMachine.State; |
| |
| @Local(value = ServerResource.class) |
| public class BareMetalResourceBase implements ServerResource { |
| private static final Logger s_logger = Logger.getLogger(BareMetalResourceBase.class); |
| protected HashMap<String, State> _vms = new HashMap<String, State>(2); |
| protected String _name; |
| protected String _uuid; |
| protected String _zone; |
| protected String _pod; |
| 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 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; |
| |
| private void changeVmState(String vmName, VirtualMachine.State state) { |
| synchronized (_vms) { |
| _vms.put(vmName, state); |
| } |
| } |
| |
| private State removeVmState(String vmName) { |
| synchronized (_vms) { |
| return _vms.remove(vmName); |
| } |
| } |
| |
| @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", (String)params.get(ApiConstants.CPU_NUMBER), |
| (String)params.get(ApiConstants.MEMORY), (String)params.get(ApiConstants.CPU_SPEED))); |
| } |
| |
| _zone = (String) params.get("zone"); |
| _pod = (String) params.get("pod"); |
| _cluster = (String) params.get("cluster"); |
| _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"); |
| |
| 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"); |
| } |
| |
| String injectScript = "scripts/util/ipmi.py"; |
| String scriptPath = Script.findScript("", injectScript); |
| if (scriptPath == null) { |
| throw new ConfigurationException("Cannot find ping script " + scriptPath); |
| } |
| _pingCommand = new Script2(scriptPath, s_logger); |
| _pingCommand.add("ping"); |
| _pingCommand.add("hostname="+_ip); |
| _pingCommand.add("usrname="+_username); |
| _pingCommand.add("password="+_password, ParamType.PASSWORD); |
| |
| _setPxeBootCommand = new Script2(scriptPath, s_logger); |
| _setPxeBootCommand.add("boot_dev"); |
| _setPxeBootCommand.add("hostname="+_ip); |
| _setPxeBootCommand.add("usrname="+_username); |
| _setPxeBootCommand.add("password="+_password, ParamType.PASSWORD); |
| _setPxeBootCommand.add("dev=pxe"); |
| |
| _setDiskBootCommand = new Script2(scriptPath, s_logger); |
| _setDiskBootCommand.add("boot_dev"); |
| _setDiskBootCommand.add("hostname="+_ip); |
| _setDiskBootCommand.add("usrname="+_username); |
| _setDiskBootCommand.add("password="+_password, ParamType.PASSWORD); |
| _setDiskBootCommand.add("dev=disk"); |
| |
| _rebootCommand = new Script2(scriptPath, s_logger); |
| _rebootCommand.add("reboot"); |
| _rebootCommand.add("hostname="+_ip); |
| _rebootCommand.add("usrname="+_username); |
| _rebootCommand.add("password="+_password, ParamType.PASSWORD); |
| |
| _getStatusCommand = new Script2(scriptPath, s_logger); |
| _getStatusCommand.add("ping"); |
| _getStatusCommand.add("hostname="+_ip); |
| _getStatusCommand.add("usrname="+_username); |
| _getStatusCommand.add("password="+_password, ParamType.PASSWORD); |
| |
| _powerOnCommand = new Script2(scriptPath, s_logger); |
| _powerOnCommand.add("power"); |
| _powerOnCommand.add("hostname="+_ip); |
| _powerOnCommand.add("usrname="+_username); |
| _powerOnCommand.add("password="+_password, ParamType.PASSWORD); |
| _powerOnCommand.add("action=on"); |
| |
| _powerOffCommand = new Script2(scriptPath, s_logger); |
| _powerOffCommand.add("power"); |
| _powerOffCommand.add("hostname="+_ip); |
| _powerOffCommand.add("usrname="+_username); |
| _powerOffCommand.add("password="+_password, ParamType.PASSWORD); |
| _powerOffCommand.add("action=soft"); |
| |
| _forcePowerOffCommand = new Script2(scriptPath, s_logger); |
| _forcePowerOffCommand.add("power"); |
| _forcePowerOffCommand.add("hostname=" + _ip); |
| _forcePowerOffCommand.add("usrname=" + _username); |
| _forcePowerOffCommand.add("password=" + _password, ParamType.PASSWORD); |
| _forcePowerOffCommand.add("action=off"); |
| |
| _bootOrRebootCommand = new Script2(scriptPath, s_logger); |
| _bootOrRebootCommand.add("boot_or_reboot"); |
| _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, OutputInterpreter interpreter) { |
| int retry = 5; |
| 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"); |
| 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 State getVmState() { |
| OutputInterpreter.AllLinesParser interpreter = new OutputInterpreter.AllLinesParser(); |
| if (!doScript(_getStatusCommand, interpreter)) { |
| s_logger.warn("Cannot get power status of " + _name + ", assume VM state was not changed"); |
| return null; |
| } |
| if (isPowerOn(interpreter.getLines())) { |
| return State.Running; |
| } else { |
| return State.Stopped; |
| } |
| } |
| |
| protected Map<String, State> fullSync() { |
| Map<String, State> changes = new HashMap<String, State>(); |
| |
| if (_vmName != null) { |
| State state = getVmState(); |
| if (state != null) { |
| changes.put(_vmName, state); |
| } |
| } |
| |
| return changes; |
| } |
| |
| @Override |
| public StartupCommand[] initialize() { |
| StartupRoutingCommand cmd = new StartupRoutingCommand(0, 0, 0, 0, null, Hypervisor.HypervisorType.BareMetal, |
| new HashMap<String, String>(), null); |
| 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); |
| cmd.setStateChanges(fullSync()); |
| 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; |
| } |
| |
| return new PingRoutingCommand(getType(), id, deltaSync()); |
| } |
| |
| 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, State.Stopped, 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"); |
| } |
| |
| @Override |
| public Answer executeRequest(Command cmd) { |
| 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 CheckNetworkCommand) { |
| return execute((CheckNetworkCommand)cmd); |
| } else { |
| return Answer.createUnsupportedCommandAnswer(cmd); |
| } |
| } |
| |
| 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) { |
| if (!doScript(_rebootCommand)) { |
| 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)) { |
| s_logger.warn("Cannot get power status of " + _name + ", assume VM state was not changed"); |
| break; |
| } |
| |
| if (!isPowerOn(interpreter.getLines())) { |
| success = true; |
| break; |
| } else { |
| powerOff = _forcePowerOffCommand; |
| } |
| |
| count++; |
| } |
| |
| return success ? new StopAnswer(cmd, "Success", null, true) : new StopAnswer(cmd, "IPMI power off failed", false); |
| } |
| |
| protected StartAnswer execute(StartCommand cmd) { |
| VirtualMachineTO vm = cmd.getVirtualMachine(); |
| State state = State.Stopped; |
| |
| try { |
| changeVmState(vm.getName(), State.Starting); |
| |
| boolean pxeBoot = false; |
| String[] bootArgs = vm.getBootArgs().split(" "); |
| for (int i = 0; i < bootArgs.length; i++) { |
| if (bootArgs[i].equalsIgnoreCase("PxeBoot")) { |
| pxeBoot = true; |
| break; |
| } |
| } |
| |
| if (pxeBoot) { |
| if (!doScript(_setPxeBootCommand)) { |
| return new StartAnswer(cmd, "Set boot device to PXE failed"); |
| } |
| s_logger.debug("Set " + vm.getHostName() + " to PXE boot successfully"); |
| } else { |
| execute(new IpmISetBootDevCommand(BootDev.disk)); |
| } |
| |
| 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 (pxeBoot) { |
| if (!doScript(_rebootCommand)) { |
| return new StartAnswer(cmd, "IPMI reboot failed"); |
| } |
| s_logger.debug("IPMI reboot " + vm.getHostName() + " successfully"); |
| } else { |
| s_logger.warn("Machine " + _name + " is alreay power on, why we still get a Start command? ignore it"); |
| |
| } |
| } else { |
| if (!doScript(_powerOnCommand)) { |
| return new StartAnswer(cmd, "IPMI power on failed"); |
| } |
| } |
| |
| s_logger.debug("Start bare metal vm " + vm.getName() + "successfully"); |
| state = State.Running; |
| _vmName = vm.getName(); |
| return new StartAnswer(cmd); |
| } finally { |
| if (state != State.Stopped) { |
| changeVmState(vm.getName(), state); |
| } else { |
| removeVmState(vm.getName()); |
| } |
| } |
| } |
| |
| protected HashMap<String, State> deltaSync() { |
| final HashMap<String, State> changes = new HashMap<String, State>(); |
| /* |
| * Disable sync until we find a way that only tracks status but not does action |
| * |
| * The scenario is: Baremetal will reboot host when creating template. Given most |
| * servers take a long time to boot up, there would be a period that mgmt server finds |
| * the host is stopped through fullsync. Then mgmt server updates database with marking the host as |
| * stopped, after that, the host comes up and full sync then indicates it's running. Because |
| * in database the host is already stopped, mgmt server sends out a stop command. |
| * As a result, creating image gets never happened. |
| * |
| if (_vmName == null) { |
| return null; |
| } |
| |
| State newState = getVmState(); |
| if (newState == null) { |
| s_logger.warn("Cannot get power state of VM " + _vmName); |
| return null; |
| } |
| |
| final State oldState = removeVmState(_vmName); |
| if (oldState == null) { |
| changeVmState(_vmName, newState); |
| changes.put(_vmName, newState); |
| } else if (oldState == State.Starting) { |
| if (newState == State.Running) { |
| changeVmState(_vmName, newState); |
| } else if (newState == State.Stopped) { |
| s_logger.debug("Ignoring vm " + _vmName + " because of a lag in starting the vm."); |
| } |
| } else if (oldState == State.Migrating) { |
| s_logger.warn("How can baremetal VM get into migrating state???"); |
| } else if (oldState == State.Stopping) { |
| if (newState == State.Stopped) { |
| changeVmState(_vmName, newState); |
| } else if (newState == State.Running) { |
| s_logger.debug("Ignoring vm " + _vmName + " because of a lag in stopping the vm. "); |
| } |
| } else if (oldState != newState) { |
| changeVmState(_vmName, newState); |
| changes.put(_vmName, newState); |
| } |
| */ |
| return changes; |
| |
| } |
| |
| 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; |
| } |
| |
| } |