From a9728998ffc1fce9aef8bd4808b05fbee7d7853d Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 25 Feb 2011 18:58:07 -0800 Subject: [PATCH] Bug 8208 - bare metal provisioning Successfully add start entry into LinMin PXE server --- .../agent/api/StartupPxeServerCommand.java | 9 + .../PrepareLinMinPxeServerAnswer.java | 14 + .../PrepareLinMinPxeServerCommand.java | 63 ++++ api/src/com/cloud/api/ApiConstants.java | 2 + api/src/com/cloud/host/Host.java | 3 +- .../com/cloud/vm/VirtualMachineProfile.java | 3 +- .../cloud/agent/manager/AgentManagerImpl.java | 6 +- server/src/com/cloud/api/AddPxeServerCmd.java | 103 ++++++ .../cloud/api/response/PxeServerResponse.java | 18 + .../baremetal/LinMinPxeServerManager.java | 5 + .../baremetal/LinMinPxeServerManagerImpl.java | 121 +++++++ .../baremetal/LinMinPxeServerResource.java | 321 ++++++++++++++++++ .../com/cloud/baremetal/PxeServerManager.java | 37 ++ .../cloud/baremetal/PxeServerManagerImpl.java | 70 ++++ .../kvm/discoverer/KvmServerDiscoverer.java | 86 +---- .../cloud/template/TemplateManagerImpl.java | 26 +- .../com/cloud/vm/BareMetalVmManagerImpl.java | 76 ++++- .../src/com/cloud/vm/UserVmManagerImpl.java | 7 + .../src/com/cloud/utils/ssh/SSHCmdHelper.java | 96 ++++++ 19 files changed, 969 insertions(+), 97 deletions(-) create mode 100644 api/src/com/cloud/agent/api/StartupPxeServerCommand.java create mode 100644 api/src/com/cloud/agent/api/baremetal/PrepareLinMinPxeServerAnswer.java create mode 100644 api/src/com/cloud/agent/api/baremetal/PrepareLinMinPxeServerCommand.java create mode 100644 server/src/com/cloud/api/AddPxeServerCmd.java create mode 100644 server/src/com/cloud/api/response/PxeServerResponse.java create mode 100644 server/src/com/cloud/baremetal/LinMinPxeServerManager.java create mode 100644 server/src/com/cloud/baremetal/LinMinPxeServerManagerImpl.java create mode 100644 server/src/com/cloud/baremetal/LinMinPxeServerResource.java create mode 100644 server/src/com/cloud/baremetal/PxeServerManager.java create mode 100644 server/src/com/cloud/baremetal/PxeServerManagerImpl.java create mode 100644 utils/src/com/cloud/utils/ssh/SSHCmdHelper.java diff --git a/api/src/com/cloud/agent/api/StartupPxeServerCommand.java b/api/src/com/cloud/agent/api/StartupPxeServerCommand.java new file mode 100644 index 00000000000..487f54c463d --- /dev/null +++ b/api/src/com/cloud/agent/api/StartupPxeServerCommand.java @@ -0,0 +1,9 @@ +package com.cloud.agent.api; + +import com.cloud.host.Host; + +public class StartupPxeServerCommand extends StartupCommand { + public StartupPxeServerCommand() { + super(Host.Type.PxeServer); + } +} diff --git a/api/src/com/cloud/agent/api/baremetal/PrepareLinMinPxeServerAnswer.java b/api/src/com/cloud/agent/api/baremetal/PrepareLinMinPxeServerAnswer.java new file mode 100644 index 00000000000..94d1b284667 --- /dev/null +++ b/api/src/com/cloud/agent/api/baremetal/PrepareLinMinPxeServerAnswer.java @@ -0,0 +1,14 @@ +package com.cloud.agent.api.baremetal; + +import com.cloud.agent.api.Answer; + +public class PrepareLinMinPxeServerAnswer extends Answer { + public PrepareLinMinPxeServerAnswer(PrepareLinMinPxeServerCommand cmd) { + super(cmd, true, "SUCCESS"); + } + + public PrepareLinMinPxeServerAnswer(PrepareLinMinPxeServerCommand cmd, String details) { + super(cmd, false, details); + } + +} diff --git a/api/src/com/cloud/agent/api/baremetal/PrepareLinMinPxeServerCommand.java b/api/src/com/cloud/agent/api/baremetal/PrepareLinMinPxeServerCommand.java new file mode 100644 index 00000000000..33a65377eff --- /dev/null +++ b/api/src/com/cloud/agent/api/baremetal/PrepareLinMinPxeServerCommand.java @@ -0,0 +1,63 @@ +package com.cloud.agent.api.baremetal; + +import com.cloud.agent.api.Command; + +public class PrepareLinMinPxeServerCommand extends Command { + String ip; + String mac; + String netMask; + String gateway; + String dns; + String template; + String vmName; + String hostName; + + @Override + public boolean executeInSequence() { + return true; + } + + public PrepareLinMinPxeServerCommand(String ip, String mac, String netMask, String gateway, String dns, String template, String vmName, String hostName) { + this.ip = ip; + this.mac = mac; + this.netMask = netMask; + this.gateway = gateway; + this.dns = dns; + this.template = template; + this.vmName = vmName; + this.hostName = hostName; + } + + public String getIp() { + return ip; + } + + public String getMac() { + return mac; + } + + public String getNetMask() { + return netMask; + } + + public String getGateWay() { + return gateway; + } + + public String getDns() { + return dns; + } + + public String getTemplate() { + return template; + } + + public String getVmName() { + return vmName; + } + + public String getHostName() { + return hostName; + } + +} diff --git a/api/src/com/cloud/api/ApiConstants.java b/api/src/com/cloud/api/ApiConstants.java index 65477ce9967..02605c7a4bd 100755 --- a/api/src/com/cloud/api/ApiConstants.java +++ b/api/src/com/cloud/api/ApiConstants.java @@ -194,5 +194,7 @@ public class ApiConstants { public static final String HOST_CPU_NUM = "hostcpunum"; public static final String HOST_MEM_CAPACITY = "hostmemcapacity"; public static final String HOST_MAC = "hostmac"; + public static final String PXE_SERVER_TYPE = "pxeservertype"; + } diff --git a/api/src/com/cloud/host/Host.java b/api/src/com/cloud/host/Host.java index ef5baf1cb53..d620e631b7f 100755 --- a/api/src/com/cloud/host/Host.java +++ b/api/src/com/cloud/host/Host.java @@ -32,7 +32,8 @@ public interface Host { SecondaryStorage(false), ConsoleProxy(true), ExternalFirewall(false), - ExternalLoadBalancer(false); + ExternalLoadBalancer(false), + PxeServer(false); boolean _virtual; private Type(boolean virtual) { diff --git a/api/src/com/cloud/vm/VirtualMachineProfile.java b/api/src/com/cloud/vm/VirtualMachineProfile.java index d3cece441ae..0d2f88b15ad 100644 --- a/api/src/com/cloud/vm/VirtualMachineProfile.java +++ b/api/src/com/cloud/vm/VirtualMachineProfile.java @@ -47,7 +47,8 @@ public interface VirtualMachineProfile { public static final Param VmPassword = new Param("VmPassword"); public static final Param ControlNic = new Param("ControlNic"); public static final Param RestartNetwork = new Param("RestartNetwork"); - + public static final Param PxeSeverType = new Param("PxeSeverType"); + private String name; public Param(String name) { diff --git a/server/src/com/cloud/agent/manager/AgentManagerImpl.java b/server/src/com/cloud/agent/manager/AgentManagerImpl.java index c95e5e375f8..237088283e7 100755 --- a/server/src/com/cloud/agent/manager/AgentManagerImpl.java +++ b/server/src/com/cloud/agent/manager/AgentManagerImpl.java @@ -63,6 +63,7 @@ import com.cloud.agent.api.StartupCommand; import com.cloud.agent.api.StartupExternalFirewallCommand; import com.cloud.agent.api.StartupExternalLoadBalancerCommand; import com.cloud.agent.api.StartupProxyCommand; +import com.cloud.agent.api.StartupPxeServerCommand; import com.cloud.agent.api.StartupRoutingCommand; import com.cloud.agent.api.StartupStorageCommand; import com.cloud.agent.api.UnsupportedAnswer; @@ -2363,6 +2364,8 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory, type = Host.Type.ExternalFirewall; } else if (startup instanceof StartupExternalLoadBalancerCommand) { type = Host.Type.ExternalLoadBalancer; + } else if (startup instanceof StartupPxeServerCommand) { + type = Host.Type.PxeServer; } else { assert false : "Did someone add a new Startup command?"; } @@ -2617,7 +2620,8 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory, if (p == null) { if (type != Host.Type.SecondaryStorage && type != Host.Type.ExternalFirewall - && type != Host.Type.ExternalLoadBalancer) { + && type != Host.Type.ExternalLoadBalancer + && type != Host.Type.PxeServer) { /* * s_logger.info("Unable to find the pod so we are creating one." diff --git a/server/src/com/cloud/api/AddPxeServerCmd.java b/server/src/com/cloud/api/AddPxeServerCmd.java new file mode 100644 index 00000000000..7778bca87ce --- /dev/null +++ b/server/src/com/cloud/api/AddPxeServerCmd.java @@ -0,0 +1,103 @@ +package com.cloud.api; + +import org.apache.log4j.Logger; + +import com.cloud.api.ApiConstants; +import com.cloud.api.BaseCmd; +import com.cloud.api.Implementation; +import com.cloud.api.Parameter; +import com.cloud.api.ServerApiException; +import com.cloud.api.response.PxeServerResponse; +import com.cloud.baremetal.LinMinPxeServerManager; +import com.cloud.baremetal.PxeServerManager; +import com.cloud.baremetal.PxeServerManager.PxeServerType; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.host.Host; +import com.cloud.server.ManagementService; +import com.cloud.utils.component.ComponentLocator; +import com.cloud.utils.exception.CloudRuntimeException; + +@Implementation(description="Adds a PXE server appliance", responseObject = PxeServerResponse.class) +public class AddPxeServerCmd extends BaseCmd { + public static final Logger s_logger = Logger.getLogger(AddPxeServerCmd.class.getName()); + private static final String s_name = "addpxeserverresponse"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name=ApiConstants.ZONE_ID, type=CommandType.LONG, required = true, description="Zone in which to add the external firewall appliance.") + private Long zoneId; + + @Parameter(name=ApiConstants.URL, type=CommandType.STRING, required = true, description="URL of the PXE server appliance.") + private String url; + + @Parameter(name=ApiConstants.USERNAME, type=CommandType.STRING, required = true, description="Username of PXE server appliance.") + private String username; + + @Parameter(name=ApiConstants.PASSWORD, type=CommandType.STRING, required = true, description="Password of the PXE server appliance.") + private String password; + + @Parameter(name=ApiConstants.PXE_SERVER_TYPE, type=CommandType.STRING, required = true, description="Type of PXE server. Current values are LinMin, DMCD") + private String type; + + /////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getZoneId() { + return zoneId; + } + + public String getUrl() { + return url; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public String getType() { + return type; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, + ResourceAllocationException { + try { + PxeServerManager pxeServerMgr; + ComponentLocator locator = ComponentLocator.getLocator(ManagementService.Name); + if (getType().equalsIgnoreCase(PxeServerType.LinMin.getName())) { + pxeServerMgr = locator.getManager(LinMinPxeServerManager.class); + } else { + throw new ServerApiException(BaseCmd.PARAM_ERROR, "Unsupport PXE server type " + getType()); + } + Host pxeServer = pxeServerMgr.addPxeServer(this); + PxeServerResponse response = pxeServerMgr.getApiResponse(pxeServer); + response.setObjectName("pxeserver"); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } catch (InvalidParameterValueException ipve) { + throw new ServerApiException(BaseCmd.PARAM_ERROR, ipve.getMessage()); + } catch (CloudRuntimeException cre) { + throw new ServerApiException(BaseCmd.INTERNAL_ERROR, cre.getMessage()); + } + } +} diff --git a/server/src/com/cloud/api/response/PxeServerResponse.java b/server/src/com/cloud/api/response/PxeServerResponse.java new file mode 100644 index 00000000000..f2f167dce45 --- /dev/null +++ b/server/src/com/cloud/api/response/PxeServerResponse.java @@ -0,0 +1,18 @@ +package com.cloud.api.response; + +import com.cloud.api.ApiConstants; +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +public class PxeServerResponse extends BaseResponse { + @SerializedName(ApiConstants.ID) @Param(description="the ID of the PXE server") + private Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } +} diff --git a/server/src/com/cloud/baremetal/LinMinPxeServerManager.java b/server/src/com/cloud/baremetal/LinMinPxeServerManager.java new file mode 100644 index 00000000000..745c89dc594 --- /dev/null +++ b/server/src/com/cloud/baremetal/LinMinPxeServerManager.java @@ -0,0 +1,5 @@ +package com.cloud.baremetal; + +public interface LinMinPxeServerManager extends PxeServerManager { + +} diff --git a/server/src/com/cloud/baremetal/LinMinPxeServerManagerImpl.java b/server/src/com/cloud/baremetal/LinMinPxeServerManagerImpl.java new file mode 100644 index 00000000000..73db98ef369 --- /dev/null +++ b/server/src/com/cloud/baremetal/LinMinPxeServerManagerImpl.java @@ -0,0 +1,121 @@ +package com.cloud.baremetal; + +import java.net.URI; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.ejb.Local; +import org.apache.log4j.Logger; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.baremetal.PrepareLinMinPxeServerAnswer; +import com.cloud.agent.api.baremetal.PrepareLinMinPxeServerCommand; +import com.cloud.api.AddPxeServerCmd; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.deploy.DeployDestination; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.resource.ServerResource; +import com.cloud.utils.component.Inject; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.NicProfile; +import com.cloud.vm.ReservationContext; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.VirtualMachineProfile; + +@Local(value = {LinMinPxeServerManager.class}) +public class LinMinPxeServerManagerImpl extends PxeServerManagerImpl implements LinMinPxeServerManager { + private static final org.apache.log4j.Logger s_logger = Logger.getLogger(LinMinPxeServerManagerImpl.class); + @Inject DataCenterDao _dcDao; + @Inject HostDao _hostDao; + @Inject AgentManager _agentMgr; + + @Override + public Host addPxeServer(AddPxeServerCmd cmd) throws InvalidParameterValueException, CloudRuntimeException { + long zoneId = cmd.getZoneId(); + + DataCenterVO zone = _dcDao.findById(zoneId); + String zoneName; + if (zone == null) { + throw new InvalidParameterValueException("Could not find zone with ID: " + zoneId); + } else { + zoneName = zone.getName(); + } + + List pxeServers = _hostDao.listByTypeDataCenter(Host.Type.PxeServer, zoneId); + if (pxeServers.size() != 0) { + throw new InvalidParameterValueException("Already had a PXE server in zone: " + zoneName); + } + + URI uri; + try { + uri = new URI(cmd.getUrl()); + } catch (Exception e) { + s_logger.debug(e); + throw new InvalidParameterValueException(e.getMessage()); + } + + String ipAddress = uri.getHost(); + String username = cmd.getUsername(); + String password = cmd.getPassword(); + String guid = getPxeServerGuid(Long.toString(zoneId), PxeServerType.LinMin.getName(), ipAddress); + Map params = new HashMap(); + params.put("zone", Long.toString(zoneId)); + params.put("ip", ipAddress); + params.put("username", username); + params.put("password", password); + params.put("guid", guid); + + ServerResource resource = null; + try { + if (cmd.getType().equalsIgnoreCase(PxeServerType.LinMin.getName())) { + resource = new LinMinPxeServerResource(); + resource.configure("LinMin PXE resource", params); + } + } catch (Exception e) { + s_logger.debug(e); + throw new CloudRuntimeException(e.getMessage()); + } + + Host pxeServer = _agentMgr.addHost(zoneId, resource, Host.Type.PxeServer, params); + if (pxeServer == null) { + throw new CloudRuntimeException("Cannot add PXE server as a host"); + } + + return pxeServer; + } + + @Override + public boolean prepare(VirtualMachineProfile profile, DeployDestination dest, ReservationContext context, Long pxeServerId) { + List nics = profile.getNics(); + if (nics.size() == 0) { + throw new CloudRuntimeException("Cannot do PXE start without nic"); + } + + NicProfile pxeNic = nics.get(0); + String mac = pxeNic.getMacAddress(); + String ip = pxeNic.getIp4Address(); + String gateway = pxeNic.getGateway(); + String mask = pxeNic.getNetmask(); + String dns = pxeNic.getDns1(); + if (dns == null) { + dns = pxeNic.getDns2(); + } + + try { + String linMinTpl = profile.getTemplate().getUrl(); + assert linMinTpl != null : "How can a null template get here!!!"; + PrepareLinMinPxeServerCommand cmd = new PrepareLinMinPxeServerCommand(ip, mac, mask, gateway, dns, linMinTpl, + profile.getVirtualMachine().getName(), dest.getHost().getName()); + PrepareLinMinPxeServerAnswer ans = (PrepareLinMinPxeServerAnswer) _agentMgr.send(pxeServerId, cmd); + return ans.getResult(); + } catch (Exception e) { + s_logger.warn("Cannot prepare PXE server", e); + return false; + } + } +} diff --git a/server/src/com/cloud/baremetal/LinMinPxeServerResource.java b/server/src/com/cloud/baremetal/LinMinPxeServerResource.java new file mode 100644 index 00000000000..99b53bd5954 --- /dev/null +++ b/server/src/com/cloud/baremetal/LinMinPxeServerResource.java @@ -0,0 +1,321 @@ +package com.cloud.baremetal; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.Map; + +import javax.naming.ConfigurationException; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.apache.log4j.Logger; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + +import com.cloud.agent.IAgentControl; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.PingCommand; +import com.cloud.agent.api.ReadyAnswer; +import com.cloud.agent.api.ReadyCommand; +import com.cloud.agent.api.StartupCommand; +import com.cloud.agent.api.StartupPxeServerCommand; +import com.cloud.agent.api.baremetal.PrepareLinMinPxeServerAnswer; +import com.cloud.agent.api.baremetal.PrepareLinMinPxeServerCommand; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.host.Host.Type; +import com.cloud.resource.ServerResource; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.ssh.SSHCmdHelper; + +public class LinMinPxeServerResource implements ServerResource { + private static final Logger s_logger = Logger.getLogger(LinMinPxeServerResource.class); + String _name; + String _guid; + String _username; + String _password; + String _ip; + String _zoneId; + + class XmlReturn { + NodeList nList; + public XmlReturn(InputSource s, String tagName) { + try { + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + Document doc = dBuilder.parse(s); + doc.getDocumentElement().normalize(); + nList = doc.getElementsByTagName(tagName); + } catch (Exception e) { + s_logger.debug("The XML file:"); + s_logger.debug(s.toString()); + s_logger.debug("Cannot parse XMl file", e); + nList = null; + } + } + + public String getValue(String tag) { + if (nList == null || nList.getLength() == 0) { + throw new InvalidParameterValueException("invalid XML file"); + } + + Element e = (Element)nList.item(0); + NodeList nlList= e.getElementsByTagName(tag).item(0).getChildNodes(); + Node nValue = (Node)nlList.item(0); + return nValue.getNodeValue(); + } + } + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + _name = name; + _guid = (String)params.get("guid"); + _ip = (String)params.get("ip"); + _username = (String)params.get("username"); + _password = (String)params.get("password"); + _zoneId = (String)params.get("zone"); + + if (_guid == null) { + throw new ConfigurationException("No Guid specified"); + } + + if (_zoneId == null) { + throw new ConfigurationException("No Zone specified"); + } + + if (_ip == null) { + throw new ConfigurationException("No IP specified"); + } + + if (_username == null) { + throw new ConfigurationException("No username specified"); + } + + if (_password == null) { + throw new ConfigurationException("No password specified"); + } + + com.trilead.ssh2.Connection sshConnection = new com.trilead.ssh2.Connection(_ip, 22); + + s_logger.debug(String.format("Trying to connect to LinMin PXE server(IP=%1$s, username=%2$s, password=%3$s", _ip, _username, _password)); + try { + sshConnection.connect(null, 60000, 60000); + if (!sshConnection.authenticateWithPassword(_username, _password)) { + s_logger.debug("SSH Failed to authenticate"); + throw new ConfigurationException(String.format("Cannot connect to LinMin PXE server(IP=%1$s, username=%2$s, password=%3$s", _ip, _username, + _password)); + } + + if (!SSHCmdHelper.sshExecuteCmd(sshConnection, "[ -d '/home/tftpboot' ] && [ -d '/usr/local/linmin' ]")) { + throw new ConfigurationException("Cannot find LinMin directory /home/tftpboot, /usr/local/linmin on PXE server"); + } + + return true; + } catch (Exception e) { + throw new ConfigurationException(e.getMessage()); + } finally { + if (sshConnection != null) { + sshConnection.close(); + } + } + } + + @Override + public boolean start() { + return true; + } + + @Override + public boolean stop() { + return true; + } + + @Override + public String getName() { + return _name; + } + + @Override + public Type getType() { + return Type.PxeServer; + } + + @Override + public StartupCommand[] initialize() { + StartupPxeServerCommand cmd = new StartupPxeServerCommand(); + cmd.setName(_name); + cmd.setDataCenter(_zoneId); + cmd.setPod(""); + cmd.setPrivateIpAddress(_ip); + cmd.setStorageIpAddress(""); + cmd.setVersion(""); + cmd.setGuid(_guid); + return new StartupCommand[]{cmd}; + } + + @Override + public PingCommand getCurrentStatus(long id) { + // TODO Auto-generated method stub + return null; + } + + protected ReadyAnswer execute(ReadyCommand cmd) { + s_logger.debug("LinMin resource " + _name + " is ready"); + return new ReadyAnswer(cmd); + } + + private InputSource httpCall(String urlStr) { + try { + s_logger.debug("Execute http call " + urlStr); + URL url = new URL(urlStr); + URLConnection conn = url.openConnection(); + conn.setReadTimeout(30000); + BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())); + StringBuffer xmlStuff = new StringBuffer(); + String line; + while ((line = in.readLine()) != null) { + xmlStuff.append(line); + } + StringReader statsReader = new StringReader(xmlStuff.toString()); + InputSource statsSource = new InputSource(statsReader); + s_logger.debug("Http call retrun:"); + s_logger.debug(xmlStuff.toString()); + return statsSource; + } catch (MalformedURLException e) { + throw new CloudRuntimeException("URL is malformed " + urlStr, e); + } catch (IOException e) { + s_logger.warn("can not do http call", e); + return null; + }catch (Exception e) { + s_logger.warn("Cannot do http call " + urlStr, e); + throw new CloudRuntimeException(e.getStackTrace().toString()); + } + } + + + protected PrepareLinMinPxeServerAnswer execute(PrepareLinMinPxeServerCommand cmd) { + String apiUserName = "root"; + String apiPassword = "password"; + String apid = "2ad644fb479871a0f5543dd6d29fe9ed"; + StringBuffer askApid = new StringBuffer(); + + askApid.append("http://"); + askApid.append(_ip); + askApid.append("/tftpboot/www/lbmp-API.php?actiontype=provision&apid="); + askApid.append(apid); + askApid.append("&auth_user="); + askApid.append(apiUserName); + askApid.append("&auth_user_pw="); + askApid.append(apiPassword); + askApid.append("&rtn_format=XML&action=authorize"); + InputSource s = httpCall(askApid.toString()); + if (s == null) { + return new PrepareLinMinPxeServerAnswer(cmd, "Http call failed"); + } + + try { + XmlReturn r = new XmlReturn(s, "LinMinBareMetalAPI"); + String res = r.getValue("actionResultsMsg"); + s_logger.debug(s.toString()); + if (!res.startsWith("Successful")) { + return new PrepareLinMinPxeServerAnswer(cmd, "Acquire APID failed"); + } + + String apid5 = r.getValue("apid"); + if (apid5 == null) { + return new PrepareLinMinPxeServerAnswer(cmd, "Cannot get 5 minutes APID " + apid5); + } + + StringBuffer addRole = new StringBuffer(); + addRole.append("http://"); + addRole.append(_ip); + addRole.append("/tftpboot/www/lbmp-API.php?actiontype=provision&user_supplied_id="); + addRole.append(cmd.getVmName()); + addRole.append("&mac_address="); + addRole.append(cmd.getMac().replaceAll(":", "%3A")); + addRole.append("&apid="); + addRole.append(apid5); + addRole.append("&control_file_template="); + addRole.append(cmd.getTemplate().replace(' ', '+')); + addRole.append("&node_name="); + addRole.append(cmd.getHostName()); + addRole.append("&node_domain="); + addRole.append(cmd.getHostName()); + addRole.append("&node_password=password"); + addRole.append("&node_time_zone=Etc%2FGMT-8"); + if (cmd.getIp() != null) { + addRole.append("&node_ip_address="); + addRole.append(cmd.getIp()); + } + if (cmd.getNetMask() != null) { + addRole.append("&node_subnet_mask="); + addRole.append(cmd.getNetMask()); + } + if (cmd.getDns() != null) { + addRole.append("&node_nameserver="); + addRole.append(cmd.getDns()); + } + if (cmd.getGateWay() != null) { + addRole.append("&node_default_gateway="); + addRole.append(cmd.getGateWay()); + } + addRole.append("&enable_provisioning_flag=nextbootonly&rtn_format=XML&action=add"); + + s = httpCall(addRole.toString()); + if (s == null) { + return new PrepareLinMinPxeServerAnswer(cmd, "Http call failed"); + } + r = new XmlReturn(s, "LinMinBareMetalAPI"); + res = r.getValue("actionResultsMsg"); + s_logger.debug(s.toString()); + if (!res.startsWith("Successful")) { + return new PrepareLinMinPxeServerAnswer(cmd, "Add LinMin role failed"); + } + } catch (Exception e) { + s_logger.warn("Cannot parse result from Lin Min server", e); + return new PrepareLinMinPxeServerAnswer(cmd, e.getMessage()); + } + + s_logger.debug("Prepare LinMin PXE server successfully"); + return new PrepareLinMinPxeServerAnswer(cmd); + } + + @Override + public Answer executeRequest(Command cmd) { + if (cmd instanceof ReadyCommand) { + return execute((ReadyCommand) cmd); + } else if (cmd instanceof PrepareLinMinPxeServerCommand) { + return execute((PrepareLinMinPxeServerCommand)cmd); + } else { + return Answer.createUnsupportedCommandAnswer(cmd); + } + } + + @Override + public void disconnected() { + // TODO Auto-generated method stub + + } + + @Override + public IAgentControl getAgentControl() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setAgentControl(IAgentControl agentControl) { + // TODO Auto-generated method stub + + } + +} diff --git a/server/src/com/cloud/baremetal/PxeServerManager.java b/server/src/com/cloud/baremetal/PxeServerManager.java new file mode 100644 index 00000000000..f1ac51aa1fb --- /dev/null +++ b/server/src/com/cloud/baremetal/PxeServerManager.java @@ -0,0 +1,37 @@ +package com.cloud.baremetal; + +import com.cloud.agent.api.baremetal.PrepareLinMinPxeServerCommand; +import com.cloud.api.AddPxeServerCmd; +import com.cloud.api.response.PxeServerResponse; +import com.cloud.deploy.DeployDestination; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.host.Host; +import com.cloud.utils.component.Manager; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.ReservationContext; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.VirtualMachineProfile; + +public interface PxeServerManager extends Manager { + public static class PxeServerType { + private String _name; + + public static final PxeServerType LinMin = new PxeServerType("LinMin"); + public static final PxeServerType DMCD = new PxeServerType("DMCD"); + + public PxeServerType(String name) { + _name = name; + } + + public String getName() { + return _name; + } + + } + + public Host addPxeServer(AddPxeServerCmd cmd) throws InvalidParameterValueException, CloudRuntimeException; + + public PxeServerResponse getApiResponse(Host pxeServer); + + public boolean prepare(VirtualMachineProfile profile, DeployDestination dest, ReservationContext context, Long pxeServerId); +} diff --git a/server/src/com/cloud/baremetal/PxeServerManagerImpl.java b/server/src/com/cloud/baremetal/PxeServerManagerImpl.java new file mode 100644 index 00000000000..77a20bf4dcd --- /dev/null +++ b/server/src/com/cloud/baremetal/PxeServerManagerImpl.java @@ -0,0 +1,70 @@ +package com.cloud.baremetal; + +import java.util.Map; + +import javax.ejb.Local; +import javax.naming.ConfigurationException; + +import org.apache.log4j.Logger; + +import com.cloud.agent.api.baremetal.PrepareLinMinPxeServerCommand; +import com.cloud.api.AddPxeServerCmd; +import com.cloud.api.response.PxeServerResponse; +import com.cloud.deploy.DeployDestination; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.host.Host; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.ReservationContext; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.VirtualMachineProfile; + +@Local(value = {PxeServerManager.class}) +public class PxeServerManagerImpl implements PxeServerManager { + private static final org.apache.log4j.Logger s_logger = Logger.getLogger(PxeServerManagerImpl.class); + protected String _name; + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + _name = name; + return true; + } + + @Override + public boolean start() { + return true; + } + + @Override + public boolean stop() { + return true; + } + + @Override + public String getName() { + return _name; + } + + protected String getPxeServerGuid(String zoneId, String name, String ip) { + return zoneId + "-" + name + "-" + ip; + } + + @Override + public Host addPxeServer(AddPxeServerCmd cmd) throws InvalidParameterValueException, CloudRuntimeException { + // TODO Auto-generated method stub + return null; + } + + @Override + public PxeServerResponse getApiResponse(Host pxeServer) { + PxeServerResponse response = new PxeServerResponse(); + response.setId(pxeServer.getId()); + return response; + } + + + @Override + public boolean prepare(VirtualMachineProfile profile, DeployDestination dest, ReservationContext context, Long pxeServerId) { + return true; + } + +} diff --git a/server/src/com/cloud/hypervisor/kvm/discoverer/KvmServerDiscoverer.java b/server/src/com/cloud/hypervisor/kvm/discoverer/KvmServerDiscoverer.java index 1c4b028929c..b881243b8ca 100644 --- a/server/src/com/cloud/hypervisor/kvm/discoverer/KvmServerDiscoverer.java +++ b/server/src/com/cloud/hypervisor/kvm/discoverer/KvmServerDiscoverer.java @@ -1,7 +1,5 @@ package com.cloud.hypervisor.kvm.discoverer; -import java.io.IOException; -import java.io.InputStream; import java.net.InetAddress; import java.net.URI; import java.util.HashMap; @@ -38,6 +36,7 @@ import com.cloud.resource.ServerResource; import com.cloud.utils.component.ComponentLocator; import com.cloud.utils.component.Inject; import com.cloud.utils.script.Script; +import com.cloud.utils.ssh.SSHCmdHelper; import com.trilead.ssh2.ChannelCondition; import com.trilead.ssh2.SCPClient; import com.trilead.ssh2.Session; @@ -101,85 +100,6 @@ public class KvmServerDiscoverer extends DiscovererBase implements Discoverer, // TODO Auto-generated method stub return false; } - - private static boolean sshExecuteCmd(com.trilead.ssh2.Connection sshConnection, String cmd) { - s_logger.debug("Executing cmd: " + cmd); - Session sshSession = null; - try { - sshSession = sshConnection.openSession(); - // There is a bug in Trilead library, wait a second before - // starting a shell and executing commands, from http://spci.st.ewi.tudelft.nl/chiron/xref/nl/tudelft/swerl/util/SSHConnection.html - Thread.sleep(1000); - - if (sshSession == null) { - return false; - } - - sshSession.execCommand(cmd); - - InputStream stdout = sshSession.getStdout(); - InputStream stderr = sshSession.getStderr(); - - - byte[] buffer = new byte[8192]; - while (true) { - if (stdout == null || stderr == null) { - return false; - } - - if ((stdout.available() == 0) && (stderr.available() == 0)) { - int conditions = sshSession.waitForCondition( - ChannelCondition.STDOUT_DATA - | ChannelCondition.STDERR_DATA - | ChannelCondition.EOF, 120000); - - if ((conditions & ChannelCondition.TIMEOUT) != 0) { - s_logger.info("Timeout while waiting for data from peer."); - break; - } - - if ((conditions & ChannelCondition.EOF) != 0) { - if ((conditions & (ChannelCondition.STDOUT_DATA | ChannelCondition.STDERR_DATA)) == 0) { - break; - } - } - } - - while (stdout.available() > 0) { - stdout.read(buffer); - } - - while (stderr.available() > 0) { - stderr.read(buffer); - } - } - - Thread.sleep(1000); - if (sshSession.getExitStatus() != 0) { - return false; - } - - return true; - } catch (IOException e) { - s_logger.debug("Executing cmd: " + cmd + " failed, due to: " + e.toString()); - return false; - } catch (InterruptedException e) { - return false; - } catch (Exception e) { - return false; - } finally { - if (sshSession != null) - sshSession.close(); - } - } - - private static boolean sshExecuteCmd(com.trilead.ssh2.Connection sshConnection, String cmd, int nTimes) { - for (int i = 0; i < nTimes; i ++) { - if (sshExecuteCmd(sshConnection, cmd)) - return true; - } - return false; - } @Override public Map> find(long dcId, @@ -222,7 +142,7 @@ public class KvmServerDiscoverer extends DiscovererBase implements Discoverer, return null; } - if (!sshExecuteCmd(sshConnection, "lsmod|grep kvm >& /dev/null", 3)) { + if (!SSHCmdHelper.sshExecuteCmd(sshConnection, "lsmod|grep kvm >& /dev/null", 3)) { s_logger.debug("It's not a KVM enabled machine"); return null; } @@ -241,7 +161,7 @@ public class KvmServerDiscoverer extends DiscovererBase implements Discoverer, parameters += " -N " + _kvmPrivateNic; } - sshExecuteCmd(sshConnection, "/usr/bin/setup_agent.sh " + parameters + " 1>&2", 3); + SSHCmdHelper.sshExecuteCmd(sshConnection, "/usr/bin/setup_agent.sh " + parameters + " 1>&2", 3); KvmDummyResourceBase kvmResource = new KvmDummyResourceBase(); Map params = new HashMap(); diff --git a/server/src/com/cloud/template/TemplateManagerImpl.java b/server/src/com/cloud/template/TemplateManagerImpl.java index 927d4bb2e6f..9a2c5e5eb02 100755 --- a/server/src/com/cloud/template/TemplateManagerImpl.java +++ b/server/src/com/cloud/template/TemplateManagerImpl.java @@ -380,13 +380,18 @@ public class TemplateManagerImpl implements TemplateManager, Manager, TemplateSe if (imgfmt == null) { throw new IllegalArgumentException("Image format is incorrect " + format + ". Supported formats are " + EnumUtils.listValues(ImageFormat.values())); } - - URI uri = new URI(url); - if ((uri.getScheme() == null) || (!uri.getScheme().equalsIgnoreCase("http") && !uri.getScheme().equalsIgnoreCase("https") && !uri.getScheme().equalsIgnoreCase("file") && !uri.getScheme().equalsIgnoreCase("baremetal"))) { - throw new IllegalArgumentException("Unsupported scheme for url: " + url); - } - - if (!uri.getScheme().equalsIgnoreCase("baremetal")) { + + String uriStr; + if (url.startsWith("baremetal://")) { + uriStr = url.substring("baremetal://".length()); + } else { + URI uri = new URI(url); + if ((uri.getScheme() == null) + || (!uri.getScheme().equalsIgnoreCase("http") && !uri.getScheme().equalsIgnoreCase("https") && !uri.getScheme() + .equalsIgnoreCase("file"))) { + throw new IllegalArgumentException("Unsupported scheme for url: " + url); + } + int port = uri.getPort(); if (!(port == 80 || port == 443 || port == -1)) { throw new IllegalArgumentException("Only ports 80 and 443 are allowed"); @@ -403,6 +408,7 @@ public class TemplateManagerImpl implements TemplateManager, Manager, TemplateSe } catch (UnknownHostException uhe) { throw new IllegalArgumentException("Unable to resolve " + host); } + uriStr = uri.toString(); } // Check that the resource limit for templates/ISOs won't be exceeded @@ -431,13 +437,13 @@ public class TemplateManagerImpl implements TemplateManager, Manager, TemplateSe } } - return create(userId, accountId, zoneId, name, displayText, isPublic, featured, isExtractable, imgfmt, diskType, uri, chksum, requiresHvm, bits, enablePassword, guestOSId, bootable, hypervisorType); + return create(userId, accountId, zoneId, name, displayText, isPublic, featured, isExtractable, imgfmt, diskType, uriStr, chksum, requiresHvm, bits, enablePassword, guestOSId, bootable, hypervisorType); } catch (URISyntaxException e) { throw new IllegalArgumentException("Invalid URL " + url); } } - private VMTemplateVO create(long userId, long accountId, Long zoneId, String name, String displayText, boolean isPublic, boolean featured, boolean isExtractable, ImageFormat format, TemplateType type, URI url, String chksum, boolean requiresHvm, int bits, boolean enablePassword, long guestOSId, boolean bootable, HypervisorType hyperType) { + private VMTemplateVO create(long userId, long accountId, Long zoneId, String name, String displayText, boolean isPublic, boolean featured, boolean isExtractable, ImageFormat format, TemplateType type, String url, String chksum, boolean requiresHvm, int bits, boolean enablePassword, long guestOSId, boolean bootable, HypervisorType hyperType) { Long id = _tmpltDao.getNextInSequence(Long.class, "id"); AccountVO account = _accountDao.findById(accountId); @@ -445,7 +451,7 @@ public class TemplateManagerImpl implements TemplateManager, Manager, TemplateSe throw new IllegalArgumentException("Only admins can create templates in all zones"); } - VMTemplateVO template = new VMTemplateVO(id, name, format, isPublic, featured, isExtractable, type, url.toString(), requiresHvm, bits, accountId, chksum, displayText, enablePassword, guestOSId, bootable, hyperType); + VMTemplateVO template = new VMTemplateVO(id, name, format, isPublic, featured, isExtractable, type, url, requiresHvm, bits, accountId, chksum, displayText, enablePassword, guestOSId, bootable, hyperType); if (zoneId == null) { List dcs = _dcDao.listAllIncludingRemoved(); diff --git a/server/src/com/cloud/vm/BareMetalVmManagerImpl.java b/server/src/com/cloud/vm/BareMetalVmManagerImpl.java index 59a71cb7caa..c438bad73ff 100644 --- a/server/src/com/cloud/vm/BareMetalVmManagerImpl.java +++ b/server/src/com/cloud/vm/BareMetalVmManagerImpl.java @@ -1,6 +1,12 @@ package com.cloud.vm; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Executors; @@ -10,12 +16,16 @@ import javax.naming.ConfigurationException; import org.apache.log4j.Logger; +import com.cloud.agent.api.baremetal.PrepareLinMinPxeServerCommand; import com.cloud.agent.manager.Commands; import com.cloud.api.commands.AttachVolumeCmd; import com.cloud.api.commands.CreateTemplateCmd; import com.cloud.api.commands.DeployVMCmd; import com.cloud.api.commands.DetachVolumeCmd; import com.cloud.api.commands.UpgradeVMCmd; +import com.cloud.baremetal.LinMinPxeServerManager; +import com.cloud.baremetal.PxeServerManager; +import com.cloud.baremetal.PxeServerManager.PxeServerType; import com.cloud.configuration.ResourceCount.ResourceType; import com.cloud.configuration.dao.ConfigurationDao; import com.cloud.dc.DataCenterVO; @@ -32,28 +42,36 @@ import com.cloud.exception.PermissionDeniedException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.exception.StorageUnavailableException; +import com.cloud.host.Host; +import com.cloud.host.HostVO; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.network.Network; import com.cloud.network.NetworkVO; +import com.cloud.network.IpAddrAllocator.IpAddr; import com.cloud.network.Networks.TrafficType; +import com.cloud.server.ManagementService; import com.cloud.service.ServiceOfferingVO; import com.cloud.storage.Storage; import com.cloud.storage.VMTemplateVO; import com.cloud.storage.Volume; import com.cloud.storage.Storage.TemplateType; import com.cloud.user.Account; +import com.cloud.user.AccountVO; import com.cloud.user.SSHKeyPair; import com.cloud.user.UserContext; +import com.cloud.user.UserVO; import com.cloud.uservm.UserVm; import com.cloud.utils.NumbersUtil; import com.cloud.utils.Pair; import com.cloud.utils.component.ComponentLocator; import com.cloud.utils.component.Manager; import com.cloud.utils.concurrency.NamedThreadFactory; +import com.cloud.utils.crypt.RSAHelper; import com.cloud.utils.db.DB; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.NetUtils; import com.cloud.vm.VirtualMachine.Type; +import com.cloud.vm.VirtualMachineProfile.Param; @Local(value={BareMetalVmManager.class, BareMetalVmService.class}) public class BareMetalVmManagerImpl extends UserVmManagerImpl implements BareMetalVmManager, BareMetalVmService, Manager { @@ -286,7 +304,30 @@ public class BareMetalVmManagerImpl extends UserVmManagerImpl implements BareMet } public UserVm startVirtualMachine(DeployVMCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException, ConcurrentOperationException { - return super.startVirtualMachine(cmd); + long vmId = cmd.getEntityId(); + UserVmVO vm = _vmDao.findById(vmId); + _vmDao.loadDetails(vm); + + List servers = _hostDao.listBy(Host.Type.PxeServer, vm.getDataCenterId()); + if (servers.size() == 0) { + throw new CloudRuntimeException("Cannot find PXE server, please make sure there is one PXE server per zone"); + } + HostVO pxeServer = servers.get(0); + + VMTemplateVO template = _templateDao.findById(vm.getTemplateId()); + if (template == null || template.getFormat() != Storage.ImageFormat.BAREMETAL) { + throw new InvalidParameterValueException("Invalid template with id = " + vm.getTemplateId()); + } + + Map params = new HashMap(); + //TODO: have to ugly harding code here + if (pxeServer.getResource().equalsIgnoreCase("com.cloud.baremetal.LinMinPxeServerResource")) { + params.put(Param.PxeSeverType, PxeServerType.LinMin); + } else { + throw new CloudRuntimeException("Unkown PXE server resource " + pxeServer.getResource()); + } + + return startVirtualMachine(cmd, params); } @Override @@ -333,6 +374,39 @@ public class BareMetalVmManagerImpl extends UserVmManagerImpl implements BareMet throw new PermissionDeniedException("The owner of " + vm + " either does not exist or is disabled: " + vm.getAccountId()); } + if (profile.getTemplate() == null) { + s_logger.debug("This is a normal IPMI start, skip prepartion of PXE server"); + return true; + } + + s_logger.debug("This is a PXE start, prepare PXE server first"); + PxeServerType pxeType = (PxeServerType) profile.getParameter(Param.PxeSeverType); + if (pxeType == null) { + throw new CloudRuntimeException("No PXE type specified"); + } + + PxeServerManager pxeMgr = null; + ComponentLocator locator = ComponentLocator.getLocator(ManagementService.Name); + if (pxeType == PxeServerType.LinMin) { + pxeMgr = locator.getManager(LinMinPxeServerManager.class); + } else { + throw new CloudRuntimeException("Unsupport PXE type " + pxeType.toString()); + } + + if (pxeMgr == null) { + throw new CloudRuntimeException("No PXE manager find for type " + pxeType.toString()); + } + + List servers = _hostDao.listBy(Host.Type.PxeServer, vm.getDataCenterId()); + if (servers.size() == 0) { + throw new CloudRuntimeException("Cannot find PXE server, please make sure there is one PXE server per zone"); + } + HostVO pxeServer = servers.get(0); + + if (!pxeMgr.prepare(profile, dest, context, pxeServer.getId())) { + throw new CloudRuntimeException("Pepare PXE server failed"); + } + return true; } diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index 0104af99c2c..eb038e3257d 100755 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -2106,6 +2106,10 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager @Override @ActionEvent (eventType=EventTypes.EVENT_VM_CREATE, eventDescription="starting Vm", async=true) public UserVm startVirtualMachine(DeployVMCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException, ConcurrentOperationException { + return startVirtualMachine(cmd, null); + } + + protected UserVm startVirtualMachine(DeployVMCmd cmd, Map additonalParams) throws ResourceUnavailableException, InsufficientCapacityException, ConcurrentOperationException { long vmId = cmd.getEntityId(); UserVmVO vm = _vmDao.findById(vmId); _vmDao.loadDetails(vm); @@ -2142,6 +2146,9 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager try { Map params = new HashMap(); + if (additonalParams != null) { + params.putAll(additonalParams); + } params.put(VirtualMachineProfile.Param.VmPassword, password); vm = _itMgr.start(vm, params, caller, owner); } finally { diff --git a/utils/src/com/cloud/utils/ssh/SSHCmdHelper.java b/utils/src/com/cloud/utils/ssh/SSHCmdHelper.java new file mode 100644 index 00000000000..2798c2d754e --- /dev/null +++ b/utils/src/com/cloud/utils/ssh/SSHCmdHelper.java @@ -0,0 +1,96 @@ +package com.cloud.utils.ssh; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.log4j.Logger; + +import com.trilead.ssh2.ChannelCondition; +import com.trilead.ssh2.Session; + +public class SSHCmdHelper { + private static final Logger s_logger = Logger.getLogger(SSHCmdHelper.class); + + public static boolean sshExecuteCmd(com.trilead.ssh2.Connection sshConnection, String cmd, int nTimes) { + for (int i = 0; i < nTimes; i ++) { + if (sshExecuteCmdOneShot(sshConnection, cmd)) + return true; + } + return false; + } + + public static boolean sshExecuteCmd(com.trilead.ssh2.Connection sshConnection, String cmd) { + return sshExecuteCmd(sshConnection, cmd, 3); + } + + public static boolean sshExecuteCmdOneShot(com.trilead.ssh2.Connection sshConnection, String cmd) { + s_logger.debug("Executing cmd: " + cmd); + Session sshSession = null; + try { + sshSession = sshConnection.openSession(); + // There is a bug in Trilead library, wait a second before + // starting a shell and executing commands, from http://spci.st.ewi.tudelft.nl/chiron/xref/nl/tudelft/swerl/util/SSHConnection.html + Thread.sleep(1000); + + if (sshSession == null) { + return false; + } + + sshSession.execCommand(cmd); + + InputStream stdout = sshSession.getStdout(); + InputStream stderr = sshSession.getStderr(); + + + byte[] buffer = new byte[8192]; + while (true) { + if (stdout == null || stderr == null) { + return false; + } + + if ((stdout.available() == 0) && (stderr.available() == 0)) { + int conditions = sshSession.waitForCondition( + ChannelCondition.STDOUT_DATA + | ChannelCondition.STDERR_DATA + | ChannelCondition.EOF, 120000); + + if ((conditions & ChannelCondition.TIMEOUT) != 0) { + s_logger.info("Timeout while waiting for data from peer."); + break; + } + + if ((conditions & ChannelCondition.EOF) != 0) { + if ((conditions & (ChannelCondition.STDOUT_DATA | ChannelCondition.STDERR_DATA)) == 0) { + break; + } + } + } + + while (stdout.available() > 0) { + stdout.read(buffer); + } + + while (stderr.available() > 0) { + stderr.read(buffer); + } + } + + Thread.sleep(1000); + if (sshSession.getExitStatus() != 0) { + return false; + } + + return true; + } catch (IOException e) { + s_logger.debug("Executing cmd: " + cmd + " failed, due to: " + e.toString()); + return false; + } catch (InterruptedException e) { + return false; + } catch (Exception e) { + return false; + } finally { + if (sshSession != null) + sshSession.close(); + } + } +}