diff --git a/api/src/com/cloud/event/EventTypes.java b/api/src/com/cloud/event/EventTypes.java index 7477152b706..3a1d58a66a6 100755 --- a/api/src/com/cloud/event/EventTypes.java +++ b/api/src/com/cloud/event/EventTypes.java @@ -454,6 +454,7 @@ public class EventTypes { public static final String EVENT_BAREMETAL_PXE_SERVER_ADD = "PHYSICAL.PXE.ADD"; public static final String EVENT_BAREMETAL_PXE_SERVER_DELETE = "PHYSICAL.PXE.DELETE"; public static final String EVENT_BAREMETAL_RCT_ADD = "BAREMETAL.RCT.ADD"; + public static final String EVENT_BAREMETAL_PROVISION_DONE = "BAREMETAL.PROVISION.DONE"; public static final String EVENT_AFFINITY_GROUP_CREATE = "AG.CREATE"; public static final String EVENT_AFFINITY_GROUP_DELETE = "AG.DELETE"; diff --git a/plugins/hypervisors/baremetal/resources/META-INF/cloudstack/core/spring-baremetal-core-context.xml b/plugins/hypervisors/baremetal/resources/META-INF/cloudstack/core/spring-baremetal-core-context.xml index ae28efad6db..a153e7fb0c6 100755 --- a/plugins/hypervisors/baremetal/resources/META-INF/cloudstack/core/spring-baremetal-core-context.xml +++ b/plugins/hypervisors/baremetal/resources/META-INF/cloudstack/core/spring-baremetal-core-context.xml @@ -28,7 +28,13 @@ > - + + + + + + + params) throws ConfigurationException { @@ -116,6 +118,7 @@ public class BaremetalManagerImpl extends ManagerBase implements BaremetalManage public List> getCommands() { List> cmds = new ArrayList>(); cmds.add(AddBaremetalHostCmd.class); + cmds.add(BaremetalProvisionDoneNotificationCmd.class); return cmds; } diff --git a/plugins/hypervisors/baremetal/src/com/cloud/baremetal/manager/BaremetalVlanManager.java b/plugins/hypervisors/baremetal/src/com/cloud/baremetal/manager/BaremetalVlanManager.java index 09215ea61f3..b2a1be950a5 100755 --- a/plugins/hypervisors/baremetal/src/com/cloud/baremetal/manager/BaremetalVlanManager.java +++ b/plugins/hypervisors/baremetal/src/com/cloud/baremetal/manager/BaremetalVlanManager.java @@ -18,6 +18,7 @@ package com.cloud.baremetal.manager; import com.cloud.baremetal.networkservice.BaremetalRctResponse; +import com.cloud.baremetal.networkservice.BaremetalSwitchBackend; import com.cloud.deploy.DeployDestination; import com.cloud.network.Network; import com.cloud.utils.component.Manager; diff --git a/plugins/hypervisors/baremetal/src/com/cloud/baremetal/manager/BaremetalVlanManagerImpl.java b/plugins/hypervisors/baremetal/src/com/cloud/baremetal/manager/BaremetalVlanManagerImpl.java index 3728a3123cf..c8473b691f6 100755 --- a/plugins/hypervisors/baremetal/src/com/cloud/baremetal/manager/BaremetalVlanManagerImpl.java +++ b/plugins/hypervisors/baremetal/src/com/cloud/baremetal/manager/BaremetalVlanManagerImpl.java @@ -48,7 +48,6 @@ import javax.inject.Inject; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; @@ -70,13 +69,17 @@ public class BaremetalVlanManagerImpl extends ManagerBase implements BaremetalVl @Inject private AccountManager acntMgr; - private Map backends = new HashMap<>(); + private Map backends; private class RackPair { BaremetalRct.Rack rack; BaremetalRct.HostEntry host; } + public void setBackends(Map backends) { + this.backends = backends; + } + @Override public BaremetalRctResponse addRct(AddBaremetalRctCmd cmd) { try { diff --git a/plugins/hypervisors/baremetal/src/com/cloud/baremetal/networkservice/BareMetalResourceBase.java b/plugins/hypervisors/baremetal/src/com/cloud/baremetal/networkservice/BareMetalResourceBase.java index 8ed50ef6de3..ab863dc44db 100755 --- a/plugins/hypervisors/baremetal/src/com/cloud/baremetal/networkservice/BareMetalResourceBase.java +++ b/plugins/hypervisors/baremetal/src/com/cloud/baremetal/networkservice/BareMetalResourceBase.java @@ -59,12 +59,15 @@ import com.cloud.host.Host.Type; import com.cloud.hypervisor.Hypervisor; import com.cloud.resource.ServerResource; import com.cloud.utils.component.ManagerBase; +import com.cloud.utils.db.QueryBuilder; +import com.cloud.utils.db.SearchCriteria; 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; import com.cloud.vm.VirtualMachine.PowerState; import com.cloud.vm.dao.VMInstanceDao; import org.apache.cloudstack.api.ApiConstants; @@ -107,6 +110,8 @@ public class BareMetalResourceBase extends ManagerBase implements ServerResource protected Script2 _bootOrRebootCommand; protected String _vmName; protected int ipmiRetryTimes = 5; + protected boolean provisionDoneNotificationOn = false; + protected int isProvisionDoneNotificationTimeout = 1800; protected ConfigurationDao configDao; protected VMInstanceDao vmDao; @@ -181,6 +186,13 @@ public class BareMetalResourceBase extends ManagerBase implements ServerResource s_logger.debug(e.getMessage(), e); } + try { + provisionDoneNotificationOn = Boolean.valueOf(configDao.getValue(Config.BaremetalProvisionDoneNotificationEnabled.key())); + isProvisionDoneNotificationTimeout = Integer.valueOf(configDao.getValue(Config.BaremetalProvisionDoneNotificationTimeout.key())); + } catch (Exception e) { + s_logger.debug(e.getMessage(), e); + } + String injectScript = "scripts/util/ipmi.py"; String scriptPath = Script.findScript("", injectScript); if (scriptPath == null) { @@ -392,7 +404,7 @@ public class BareMetalResourceBase extends ManagerBase implements ServerResource VMInstanceVO vm = vms.get(0); SecurityGroupHttpClient client = new SecurityGroupHttpClient(); HashMap> nwGrpStates = client.sync(vm.getInstanceName(), vm.getId(), vm.getPrivateIpAddress()); - return new PingRoutingWithNwGroupsCommand(getType(), id, getHostVmStateReport(), nwGrpStates); + return new PingRoutingWithNwGroupsCommand(getType(), id, null, nwGrpStates); } } else { return new PingRoutingCommand(getType(), id, null); @@ -581,6 +593,39 @@ public class BareMetalResourceBase extends ManagerBase implements ServerResource } } + if (provisionDoneNotificationOn) { + QueryBuilder q = QueryBuilder.create(VMInstanceVO.class); + q.and(q.entity().getInstanceName(), SearchCriteria.Op.EQ, vm.getName()); + VMInstanceVO vmvo = q.find(); + + if (vmvo.getLastHostId() == null) { + // this is new created vm + long timeout = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(isProvisionDoneNotificationTimeout); + while (timeout > System.currentTimeMillis()) { + try { + TimeUnit.SECONDS.sleep(5); + } catch (InterruptedException e) { + s_logger.warn(e.getMessage(), e); + } + + q = QueryBuilder.create(VMInstanceVO.class); + q.and(q.entity().getInstanceName(), SearchCriteria.Op.EQ, vm.getName()); + vmvo = q.find(); + if (vmvo == null) { + return new StartAnswer(cmd, String.format("cannot find vm[name:%s] while waiting for baremtal provision done notification", vm.getName())); + } + + if (VirtualMachine.State.Running == vmvo.getState()) { + return new StartAnswer(cmd); + } + + s_logger.debug(String.format("still wait for baremetal provision done notification for vm[name:%s], current vm state is %s", vmvo.getInstanceName(), vmvo.getState())); + } + + return new StartAnswer(cmd, String.format("timeout after %s seconds, no baremetal provision done notification received. vm[name:%s] failed to start", isProvisionDoneNotificationTimeout, vm.getName())); + } + } + s_logger.debug("Start bare metal vm " + vm.getName() + "successfully"); _vmName = vm.getName(); return new StartAnswer(cmd); diff --git a/plugins/hypervisors/baremetal/src/com/cloud/baremetal/networkservice/BaremetalKickStartPxeResource.java b/plugins/hypervisors/baremetal/src/com/cloud/baremetal/networkservice/BaremetalKickStartPxeResource.java index 75be5970431..260f4f16e87 100755 --- a/plugins/hypervisors/baremetal/src/com/cloud/baremetal/networkservice/BaremetalKickStartPxeResource.java +++ b/plugins/hypervisors/baremetal/src/com/cloud/baremetal/networkservice/BaremetalKickStartPxeResource.java @@ -34,7 +34,6 @@ import com.cloud.agent.api.Command; import com.cloud.agent.api.HostVmStateReportEntry; import com.cloud.agent.api.PingCommand; import com.cloud.agent.api.PingRoutingCommand; -import com.cloud.agent.api.baremetal.PrepareKickstartPxeServerCommand; import com.cloud.agent.api.routing.VmDataCommand; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.script.Script; diff --git a/plugins/hypervisors/baremetal/src/com/cloud/baremetal/networkservice/BaremetalKickStartServiceImpl.java b/plugins/hypervisors/baremetal/src/com/cloud/baremetal/networkservice/BaremetalKickStartServiceImpl.java index f668155c522..7f6b0bf3f6a 100755 --- a/plugins/hypervisors/baremetal/src/com/cloud/baremetal/networkservice/BaremetalKickStartServiceImpl.java +++ b/plugins/hypervisors/baremetal/src/com/cloud/baremetal/networkservice/BaremetalKickStartServiceImpl.java @@ -30,16 +30,17 @@ import java.util.Map; import javax.ejb.Local; import javax.inject.Inject; +import com.cloud.configuration.Config; +import org.apache.log4j.Logger; + import org.apache.cloudstack.api.AddBaremetalKickStartPxeCmd; import org.apache.cloudstack.api.AddBaremetalPxeCmd; import org.apache.cloudstack.api.ListBaremetalPxeServersCmd; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; -import org.apache.log4j.Logger; import com.cloud.agent.api.Answer; import com.cloud.agent.api.baremetal.IpmISetBootDevCommand; import com.cloud.agent.api.baremetal.IpmISetBootDevCommand.BootDev; -import com.cloud.agent.api.baremetal.PrepareKickstartPxeServerCommand; import com.cloud.baremetal.database.BaremetalPxeDao; import com.cloud.baremetal.database.BaremetalPxeVO; import com.cloud.baremetal.networkservice.BaremetalPxeManager.BaremetalPxeType; @@ -223,6 +224,11 @@ public class BaremetalKickStartServiceImpl extends BareMetalPxeServiceBase imple throw new CloudRuntimeException(String.format("cannot find management nic on virtual router[id:%s]", vr.getId())); } + String internalServerIp = _configDao.getValue(Config.BaremetalInternalStorageServer.key()); + if (internalServerIp == null) { + throw new CloudRuntimeException(String.format("please specify 'baremetal.internal.storage.server.ip', which is the http server/nfs server storing kickstart files and ISO files, in global setting")); + } + List tuple = parseKickstartUrl(profile); String cmd = String.format("/opt/cloud/bin/prepare_pxe.sh %s %s %s %s %s %s", tuple.get(1), tuple.get(2), profile.getTemplate().getUuid(), String.format("01-%s", nic.getMacAddress().replaceAll(":", "-")).toLowerCase(), tuple.get(0), nic.getMacAddress().toLowerCase()); @@ -232,7 +238,7 @@ public class BaremetalKickStartServiceImpl extends BareMetalPxeServiceBase imple throw new CloudRuntimeException(String.format("failed preparing PXE in virtual router[id:%s], because %s", vr.getId(), ret.second())); } - String internalServerIp = "10.223.110.231"; + //String internalServerIp = "10.223.110.231"; cmd = String.format("/opt/cloud/bin/baremetal_snat.sh %s %s %s", mgmtNic.getIp4Address(), internalServerIp, mgmtNic.getGateway()); s_logger.debug(String.format("prepare SNAT on virtual router[ip:%s], cmd: %s", mgmtNic.getIp4Address(), cmd)); ret = SshHelper.sshExecute(mgmtNic.getIp4Address(), 3922, "root", getSystemVMKeyFile(), null, cmd); diff --git a/plugins/hypervisors/baremetal/src/com/cloud/baremetal/networkservice/BaremetalPxeElement.java b/plugins/hypervisors/baremetal/src/com/cloud/baremetal/networkservice/BaremetalPxeElement.java index 5d20d3130f5..10b81c2b3d7 100755 --- a/plugins/hypervisors/baremetal/src/com/cloud/baremetal/networkservice/BaremetalPxeElement.java +++ b/plugins/hypervisors/baremetal/src/com/cloud/baremetal/networkservice/BaremetalPxeElement.java @@ -21,6 +21,7 @@ package com.cloud.baremetal.networkservice; import com.cloud.baremetal.database.BaremetalPxeVO; import com.cloud.baremetal.manager.BaremetalVlanManager; import com.cloud.dc.DataCenter; +import com.cloud.dc.DataCenterVO; import com.cloud.dc.Pod; import com.cloud.dc.dao.DataCenterDao; import com.cloud.deploy.DeployDestination; @@ -139,11 +140,9 @@ public class BaremetalPxeElement extends AdapterBase implements NetworkElement { } } - /* if (dest.getDataCenter().getNetworkType() == DataCenter.NetworkType.Advanced){ prepareVlan(network, dest); } - */ return true; } @@ -159,12 +158,10 @@ public class BaremetalPxeElement extends AdapterBase implements NetworkElement { return false; } - /* DataCenterVO dc = zoneDao.findById(vm.getVirtualMachine().getDataCenterId()); if (dc.getNetworkType() == DataCenter.NetworkType.Advanced) { releaseVlan(network, vm); } - */ return true; } diff --git a/plugins/hypervisors/baremetal/src/com/cloud/baremetal/networkservice/Force10BaremetalSwitchBackend.java b/plugins/hypervisors/baremetal/src/com/cloud/baremetal/networkservice/Force10BaremetalSwitchBackend.java old mode 100644 new mode 100755 index 9009b02d3fb..c34a6cd15a0 --- a/plugins/hypervisors/baremetal/src/com/cloud/baremetal/networkservice/Force10BaremetalSwitchBackend.java +++ b/plugins/hypervisors/baremetal/src/com/cloud/baremetal/networkservice/Force10BaremetalSwitchBackend.java @@ -32,11 +32,16 @@ import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; +import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -46,17 +51,38 @@ public class Force10BaremetalSwitchBackend implements BaremetalSwitchBackend { private Logger logger = Logger.getLogger(Force10BaremetalSwitchBackend.class); public static final String TYPE = "Force10"; + private static List successHttpStatusCode = new ArrayList<>(); + { + successHttpStatusCode.add(HttpStatus.OK); + successHttpStatusCode.add(HttpStatus.ACCEPTED); + successHttpStatusCode.add(HttpStatus.CREATED); + successHttpStatusCode.add(HttpStatus.NO_CONTENT); + successHttpStatusCode.add(HttpStatus.PARTIAL_CONTENT); + successHttpStatusCode.add(HttpStatus.RESET_CONTENT); + successHttpStatusCode.add(HttpStatus.ALREADY_REPORTED); + } + RestTemplate rest = new RestTemplate(); + { + // fake error handler, we handle error in business logic code + rest.setErrorHandler(new ResponseErrorHandler() { + @Override + public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException { + return false; + } - private String buildLink(String switchIp, Integer vlan) { + @Override + public void handleError(ClientHttpResponse clientHttpResponse) throws IOException { + } + }); + } + + private String buildLink(String switchIp, String path) { UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); builder.scheme("http"); builder.host(switchIp); builder.port(8008); - builder.path("/api/running/ftos/interface/vlan"); - if (vlan != null) { - builder.path(vlan.toString()); - } + builder.path(path); return builder.build().toUriString(); } @@ -67,29 +93,36 @@ public class Force10BaremetalSwitchBackend implements BaremetalSwitchBackend { @Override public void prepareVlan(BaremetalVlanStruct struct) { - String link = buildLink(struct.getSwitchIp(), struct.getVlan()); + String link = buildLink(struct.getSwitchIp(), String.format("/api/running/ftos/interface/vlan/%s", struct.getVlan())); HttpHeaders headers = createBasicAuthenticationHeader(struct); HttpEntity request = new HttpEntity<>(headers); ResponseEntity rsp = rest.exchange(link, HttpMethod.GET, request, String.class); + logger.debug(String.format("http get: %s", link)); if (rsp.getStatusCode() == HttpStatus.NOT_FOUND) { PortInfo port = new PortInfo(struct); - XmlObject xml = new XmlObject("vlan").putElement("vlan-id", String.valueOf(struct.getVlan())).putElement("tagged", - new XmlObject(port.interfaceType).putElement("name", port.port) - ).putElement("shutdown", "false"); - request = new HttpEntity<>(xml.toString(), headers); - link = buildLink(struct.getSwitchIp(), null); - rsp = rest.exchange(link, HttpMethod.GET, request, String.class); - if (rsp.getStatusCode() != HttpStatus.OK) { + XmlObject xml = new XmlObject("vlan").putElement("vlan-id", + new XmlObject("vlan-id").setText(String.valueOf(struct.getVlan()))).putElement("untagged", + new XmlObject("untagged").putElement(port.interfaceType, new XmlObject(port.interfaceType) + .putElement("name", new XmlObject("name").setText(port.port))) + ).putElement("shutdown", new XmlObject("shutdown").setText("false")); + request = new HttpEntity<>(xml.dump(), headers); + link = buildLink(struct.getSwitchIp(), String.format("/api/running/ftos/interface/")); + logger.debug(String.format("http get: %s, body: %s", link, request)); + rsp = rest.exchange(link, HttpMethod.POST, request, String.class); + if (!successHttpStatusCode.contains(rsp.getStatusCode())) { throw new CloudRuntimeException(String.format("unable to create vlan[%s] on force10 switch[ip:%s]. HTTP status code:%s, body dump:%s", - struct.getVlan(), rsp.getStatusCode(), struct.getSwitchIp(), rsp.getBody())); + struct.getVlan(), struct.getSwitchIp(),rsp.getStatusCode(), rsp.getBody())); + } else { + logger.debug(String.format("successfully programmed vlan[%s] on Force10[ip:%s, port:%s]. http response[status code:%s, body:%s]", + struct.getVlan(), struct.getSwitchIp(), struct.getPort(), rsp.getStatusCode(), rsp.getBody())); } - } else if (rsp.getStatusCode() == HttpStatus.OK) { + } else if (successHttpStatusCode.contains(rsp.getStatusCode())) { PortInfo port = new PortInfo(struct); XmlObject xml = XmlObjectParser.parseFromString((String)rsp.getBody()); - List ports = xml.getAsList("tagged.tengigabitethernet"); - ports.addAll(xml.getAsList("tagged.gigabitethernet")); - ports.addAll(xml.getAsList("tagged.fortyGigE")); + List ports = xml.getAsList("untagged.tengigabitethernet"); + ports.addAll(xml.getAsList("untagged.gigabitethernet")); + ports.addAll(xml.getAsList("untagged.fortyGigE")); for (XmlObject pxml : ports) { XmlObject name = pxml.get("name"); if (port.port.equals(name.getText())) { @@ -98,14 +131,26 @@ public class Force10BaremetalSwitchBackend implements BaremetalSwitchBackend { } } - XmlObject tag = xml.get("tagged"); - tag.putElement(port.interfaceType, new XmlObject("name").setText(port.port)); - request = new HttpEntity<>(xml.toString(), headers); - link = buildLink(struct.getSwitchIp(), struct.getVlan()); + xml.removeElement("mtu"); + xml.setText(null); + XmlObject tag = xml.get("untagged"); + if (tag == null) { + tag = new XmlObject("untagged"); + xml.putElement("untagged", tag); + } + + tag.putElement(port.interfaceType, new XmlObject(port.interfaceType) + .putElement("name", new XmlObject("name").setText(port.port))); + request = new HttpEntity<>(xml.dump(), headers); + link = buildLink(struct.getSwitchIp(), String.format("/api/running/ftos/interface/vlan/%s", struct.getVlan())); + logger.debug(String.format("http get: %s, body: %s", link, request)); rsp = rest.exchange(link, HttpMethod.PUT, request, String.class); - if (rsp.getStatusCode() != HttpStatus.NO_CONTENT) { + if (!successHttpStatusCode.contains(rsp.getStatusCode())) { throw new CloudRuntimeException(String.format("failed to program vlan[%s] for port[%s] on force10[ip:%s]. http status:%s, body dump:%s", struct.getVlan(), struct.getPort(), struct.getSwitchIp(), rsp.getStatusCode(), rsp.getBody())); + } else { + logger.debug(String.format("successfully join port[%s] into vlan[%s] on Force10[ip:%s]. http response[status code:%s, body:%s]", + struct.getPort(), struct.getVlan(), struct.getSwitchIp(), rsp.getStatusCode(), rsp.getBody())); } } else { throw new CloudRuntimeException(String.format("force10[ip:%s] returns unexpected error[%s] when http getting %s, body dump:%s", @@ -115,18 +160,19 @@ public class Force10BaremetalSwitchBackend implements BaremetalSwitchBackend { @Override public void removePortFromVlan(BaremetalVlanStruct struct) { - String link = buildLink(struct.getSwitchIp(), struct.getVlan()); + String link = buildLink(struct.getSwitchIp(), String.format("/api/running/ftos/interface/vlan/%s", struct.getVlan())); HttpHeaders headers = createBasicAuthenticationHeader(struct); HttpEntity request = new HttpEntity<>(headers); + logger.debug(String.format("http get: %s, body: %s", link, request)); ResponseEntity rsp = rest.exchange(link, HttpMethod.GET, request, String.class); if (rsp.getStatusCode() == HttpStatus.NOT_FOUND) { logger.debug(String.format("vlan[%s] has been deleted on force10[ip:%s], no need to remove the port[%s] anymore", struct.getVlan(), struct.getSwitchIp(), struct.getPort())); } else if (rsp.getStatusCode() == HttpStatus.OK) { PortInfo port = new PortInfo(struct); XmlObject xml = XmlObjectParser.parseFromString((String)rsp.getBody()); - List ports = xml.getAsList("tagged.tengigabitethernet"); - ports.addAll(xml.getAsList("tagged.gigabitethernet")); - ports.addAll(xml.getAsList("tagged.fortyGigE")); + List ports = xml.getAsList("untagged.tengigabitethernet"); + ports.addAll(xml.getAsList("untagged.gigabitethernet")); + ports.addAll(xml.getAsList("untagged.fortyGigE")); List newPorts = new ArrayList<>(); boolean needRemove = false; for (XmlObject pxml : ports) { @@ -143,11 +189,19 @@ public class Force10BaremetalSwitchBackend implements BaremetalSwitchBackend { return; } - xml.putElement("tagged", newPorts); + xml.setText(null); + xml.removeElement("mtu"); + XmlObject tagged = xml.get("untagged"); + tagged.removeAllChildren(); + for (XmlObject p : newPorts) { + tagged.putElement(p.getTag(), p); + } + - request = new HttpEntity<>(xml.toString(), headers); + request = new HttpEntity<>(xml.dump(), headers); + logger.debug(String.format("http get: %s, body: %s", link, request)); rsp = rest.exchange(link, HttpMethod.PUT, request, String.class); - if (rsp.getStatusCode() != HttpStatus.NO_CONTENT) { + if (!successHttpStatusCode.contains(rsp.getStatusCode())) { throw new CloudRuntimeException(String.format("failed to program vlan[%s] for port[%s] on force10[ip:%s]. http status:%s, body dump:%s", struct.getVlan(), struct.getPort(), struct.getSwitchIp(), rsp.getStatusCode(), rsp.getBody())); } else { @@ -166,6 +220,8 @@ public class Force10BaremetalSwitchBackend implements BaremetalSwitchBackend { String base64Creds = new String(base64CredsBytes); HttpHeaders headers = new HttpHeaders(); headers.add("Authorization", "Basic " + base64Creds); + headers.setAccept(Arrays.asList(MediaType.ALL)); + headers.setContentType(MediaType.valueOf("application/vnd.yang.data+xml")); return headers; } diff --git a/core/src/com/cloud/agent/api/baremetal/PrepareKickstartPxeServerCommand.java b/plugins/hypervisors/baremetal/src/com/cloud/baremetal/networkservice/PrepareKickstartPxeServerCommand.java similarity index 95% rename from core/src/com/cloud/agent/api/baremetal/PrepareKickstartPxeServerCommand.java rename to plugins/hypervisors/baremetal/src/com/cloud/baremetal/networkservice/PrepareKickstartPxeServerCommand.java index b8fb5d04dd2..4f59a833187 100755 --- a/core/src/com/cloud/agent/api/baremetal/PrepareKickstartPxeServerCommand.java +++ b/plugins/hypervisors/baremetal/src/com/cloud/baremetal/networkservice/PrepareKickstartPxeServerCommand.java @@ -1,4 +1,3 @@ -// // 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 @@ -16,8 +15,8 @@ // specific language governing permissions and limitations // under the License. // - -package com.cloud.agent.api.baremetal; +// Automatically generated by addcopyright.py at 01/29/2013 +package com.cloud.baremetal.networkservice; import com.cloud.agent.api.Command; diff --git a/plugins/hypervisors/baremetal/src/org/apache/cloudstack/api/BaremetalProvisionDoneNotificationCmd.java b/plugins/hypervisors/baremetal/src/org/apache/cloudstack/api/BaremetalProvisionDoneNotificationCmd.java new file mode 100755 index 00000000000..a5248aaec65 --- /dev/null +++ b/plugins/hypervisors/baremetal/src/org/apache/cloudstack/api/BaremetalProvisionDoneNotificationCmd.java @@ -0,0 +1,87 @@ +// 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 org.apache.cloudstack.api; + +import com.cloud.baremetal.manager.BaremetalManager; +import com.cloud.event.EventTypes; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; + +import javax.inject.Inject; +import org.apache.log4j.Logger; + +/** + * Created by frank on 9/17/14. + */ +@APICommand(name = "notifyBaremetalProvisionDone", description = "Notify provision has been done on a host. This api is for baremetal virtual router service, not for end user", responseObject = SuccessResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = {RoleType.User}) +public class BaremetalProvisionDoneNotificationCmd extends BaseAsyncCmd { + public static final Logger s_logger = Logger.getLogger(BaremetalProvisionDoneNotificationCmd.class); + private static final String s_name = "baremetalprovisiondone"; + + @Inject + private BaremetalManager bmMgr; + + @Parameter(name="mac", required = true, description = "mac of the nic used for provision") + private String mac; + + @Override + public String getEventType() { + return EventTypes.EVENT_BAREMETAL_PROVISION_DONE; + } + + @Override + public String getEventDescription() { + return "notify management server that baremetal provision has been done on a host"; + } + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + try { + bmMgr.notifyProvisionDone(this); + this.setResponseObject(new SuccessResponse(getCommandName())); + } catch (Exception e) { + s_logger.warn(String.format("unable to notify baremetal provision done[mac:%s]", mac), e); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + public String getMac() { + return mac; + } + + public void setMac(String mac) { + this.mac = mac; + } + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } +} diff --git a/server/src/com/cloud/configuration/Config.java b/server/src/com/cloud/configuration/Config.java index 71f5c5b6da3..3710e0abe41 100755 --- a/server/src/com/cloud/configuration/Config.java +++ b/server/src/com/cloud/configuration/Config.java @@ -1848,22 +1848,6 @@ public enum Config { "The maximum number of retrying times to search for an available IPv6 address in the table", null), - BaremetalEnableCompleteNotification( - "Advanced", - ManagementServer.class, - Boolean.class, - "baremetal.provision.done.notification", - "false", - "Enable provision done notification through virtual router", - null), - BaremetalPeerHypervisorType( - "Advanced", - ManagementServer.class, - String.class, - "baremetal.peer.hypervisor.type", - "Vmware", - "Hypervisor[Xenserver/KVM/VMWare] used to spring up virtual router for baremetal instances. The cluster having this hypervisor type must be in the same zone with baremetal cluster", - null), BaremetalInternalStorageServer( "Advanced", ManagementServer.class, @@ -1872,6 +1856,30 @@ public enum Config { null, "the ip address of server that stores kickstart file, kernel, initrd, ISO for advanced networking baremetal provisioning", null), + BaremetalProvisionDoneNotificationEnabled( + "Advanced", + ManagementServer.class, + Boolean.class, + "baremetal.provision.done.notification.enabled", + "true", + "whether to enable baremetal provison done notification", + null), + BaremetalProvisionDoneNotificationTimeout( + "Advanced", + ManagementServer.class, + Integer.class, + "baremetal.provision.done.notification.timeout", + "1800", + "the max time to wait before treating a baremetal provision as failure if no provision done notification is not received, in secs", + null), + BaremetalProvisionDoneNotificationPort( + "Advanced", + ManagementServer.class, + Integer.class, + "baremetal.provision.done.notification.port", + "8080", + "the port that listens baremetal provision done notification. Should be the same to port management server listening on for now. Please change it to management server port if it's not default 8080", + null), ExternalBaremetalSystemUrl( "Advanced", ManagementServer.class, diff --git a/server/src/com/cloud/deploy/DeploymentPlanningManagerImpl.java b/server/src/com/cloud/deploy/DeploymentPlanningManagerImpl.java index 3329ff2c54b..bfb33b0c624 100755 --- a/server/src/com/cloud/deploy/DeploymentPlanningManagerImpl.java +++ b/server/src/com/cloud/deploy/DeploymentPlanningManagerImpl.java @@ -404,6 +404,15 @@ StateListener { s_logger.debug("The last host of this VM is UP and has enough capacity"); s_logger.debug("Now checking for suitable pools under zone: " + host.getDataCenterId() + ", pod: " + host.getPodId() + ", cluster: " + host.getClusterId()); + + Pod pod = _podDao.findById(host.getPodId()); + Cluster cluster = _clusterDao.findById(host.getClusterId()); + if (vm.getHypervisorType() == HypervisorType.BareMetal) { + DeployDestination dest = new DeployDestination(dc, pod, cluster, host, new HashMap()); + s_logger.debug("Returning Deployment Destination: " + dest); + return dest; + } + // search for storage under the zone, pod, cluster // of // the last host. @@ -423,8 +432,6 @@ StateListener { suitableHosts, suitableVolumeStoragePools, avoids, getPlannerUsage(planner, vmProfile, plan, avoids), readyAndReusedVolumes); if (potentialResources != null) { - Pod pod = _podDao.findById(host.getPodId()); - Cluster cluster = _clusterDao.findById(host.getClusterId()); Map storageVolMap = potentialResources.second(); // remove the reused vol<->pool from // destination, since we don't have to diff --git a/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java b/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java index be7cb376c97..315bdde509a 100644 --- a/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java +++ b/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java @@ -1506,6 +1506,8 @@ Configurable, StateListener { } else { buf.append(String.format(" baremetalnotificationsecuritykey=%s", user.getSecretKey())); buf.append(String.format(" baremetalnotificationapikey=%s", user.getApiKey())); + buf.append(" host=").append(ApiServiceConfiguration.ManagementHostIPAdr.value()); + buf.append(" port=").append(_configDao.getValue(Config.BaremetalProvisionDoneNotificationPort.key())); } } diff --git a/server/src/com/cloud/resource/ResourceManagerImpl.java b/server/src/com/cloud/resource/ResourceManagerImpl.java index cdad91ec376..c7ba80a07b7 100755 --- a/server/src/com/cloud/resource/ResourceManagerImpl.java +++ b/server/src/com/cloud/resource/ResourceManagerImpl.java @@ -1631,21 +1631,6 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager, clusterId = c.getId(); } } - if (startup instanceof StartupRoutingCommand) { - StartupRoutingCommand ssCmd = ((StartupRoutingCommand)startup); - List implicitHostTags = ssCmd.getHostTags(); - if (!implicitHostTags.isEmpty()) { - if (hostTags == null) { - hostTags = _hostTagsDao.gethostTags(host.getId()); - } - if (hostTags != null) { - implicitHostTags.removeAll(hostTags); - hostTags.addAll(implicitHostTags); - } else { - hostTags = implicitHostTags; - } - } - } if (startup instanceof StartupRoutingCommand) { StartupRoutingCommand ssCmd = ((StartupRoutingCommand)startup); diff --git a/server/src/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/com/cloud/storage/VolumeApiServiceImpl.java index 9f132c397a6..e089b77a37c 100644 --- a/server/src/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/com/cloud/storage/VolumeApiServiceImpl.java @@ -1230,10 +1230,9 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic // Check that the volume ID is valid VolumeInfo volumeToAttach = volFactory.getVolume(volumeId); - // Check that the volume is a data volume - if (volumeToAttach == null || !(volumeToAttach.getVolumeType() == Volume.Type.DATADISK)) { - throw new InvalidParameterValueException("Please specify a volume with the valid type: " + Volume.Type.DATADISK.toString()); + if (volumeToAttach == null || !(volumeToAttach.getVolumeType() == Volume.Type.DATADISK || volumeToAttach.getVolumeType() == Volume.Type.ROOT)) { + throw new InvalidParameterValueException("Please specify a volume with the valid type: " + Volume.Type.ROOT.toString() + " or " + Volume.Type.DATADISK.toString()); } // Check that the volume is not currently attached to any VM @@ -1516,10 +1515,9 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic throw new InvalidParameterValueException("Please specify a VM that is either running or stopped."); } - // Check that the volume is a data volume. - // TODO - Disabling root volume detach for now, enable it back in 4.6 - if (volume.getVolumeType() != Volume.Type.DATADISK) { - throw new InvalidParameterValueException("Please specify volume of type " + Volume.Type.DATADISK.toString()); + // Check that the volume is a data/root volume + if (!(volume.getVolumeType() == Volume.Type.ROOT || volume.getVolumeType() == Volume.Type.DATADISK)) { + throw new InvalidParameterValueException("Please specify volume of type " + Volume.Type.DATADISK.toString() + " or " + Volume.Type.ROOT.toString()); } // Root volume detach is allowed for following hypervisors: Xen/KVM/VmWare diff --git a/server/test/com/cloud/storage/VolumeApiServiceImplTest.java b/server/test/com/cloud/storage/VolumeApiServiceImplTest.java index 3d2de4e313d..c60aa503451 100644 --- a/server/test/com/cloud/storage/VolumeApiServiceImplTest.java +++ b/server/test/com/cloud/storage/VolumeApiServiceImplTest.java @@ -30,6 +30,7 @@ import javax.inject.Inject; import org.junit.After; import org.junit.Before; import org.junit.Rule; +import org.junit.Test; import org.junit.rules.ExpectedException; import org.mockito.Mock; import org.mockito.Mockito; @@ -48,6 +49,7 @@ import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import com.cloud.exception.InvalidParameterValueException; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.storage.dao.VolumeDao; import com.cloud.user.Account; @@ -241,7 +243,7 @@ public class VolumeApiServiceImplTest { * @throws Exception */ - //@Test(expected = InvalidParameterValueException.class) + @Test(expected = InvalidParameterValueException.class) public void testDetachVolumeFromRunningVm() throws NoSuchFieldException, IllegalAccessException { Field dedicateIdField = _detachCmdClass.getDeclaredField("id"); dedicateIdField.setAccessible(true); @@ -249,7 +251,7 @@ public class VolumeApiServiceImplTest { _svc.detachVolumeFromVM(detachCmd); } - //@Test(expected = InvalidParameterValueException.class) + @Test(expected = InvalidParameterValueException.class) public void testDetachVolumeFromStoppedHyperVVm() throws NoSuchFieldException, IllegalAccessException { Field dedicateIdField = _detachCmdClass.getDeclaredField("id"); dedicateIdField.setAccessible(true); @@ -257,7 +259,7 @@ public class VolumeApiServiceImplTest { _svc.detachVolumeFromVM(detachCmd); } - //@Test(expected = InvalidParameterValueException.class) + @Test(expected = InvalidParameterValueException.class) public void testDetachVolumeOfManagedDataStore() throws NoSuchFieldException, IllegalAccessException { Field dedicateIdField = _detachCmdClass.getDeclaredField("id"); dedicateIdField.setAccessible(true); @@ -268,7 +270,7 @@ public class VolumeApiServiceImplTest { @Rule public ExpectedException thrown = ExpectedException.none(); - //@Test + @Test public void testDetachVolumeFromStoppedXenVm() throws NoSuchFieldException, IllegalAccessException { thrown.expect(NullPointerException.class); Field dedicateIdField = _detachCmdClass.getDeclaredField("id"); @@ -282,43 +284,43 @@ public class VolumeApiServiceImplTest { */ // Negative test - try to attach non-root non-datadisk volume - //@Test(expected = InvalidParameterValueException.class) + @Test(expected = InvalidParameterValueException.class) public void attachIncorrectDiskType() throws NoSuchFieldException, IllegalAccessException { _svc.attachVolumeToVM(1L, 5L, 0L); } // Negative test - attach root volume to running vm - //@Test(expected = InvalidParameterValueException.class) + @Test(expected = InvalidParameterValueException.class) public void attachRootDiskToRunningVm() throws NoSuchFieldException, IllegalAccessException { _svc.attachVolumeToVM(1L, 6L, 0L); } // Negative test - attach root volume to non-xen vm - //@Test(expected = InvalidParameterValueException.class) + @Test(expected = InvalidParameterValueException.class) public void attachRootDiskToHyperVm() throws NoSuchFieldException, IllegalAccessException { _svc.attachVolumeToVM(3L, 6L, 0L); } // Negative test - attach root volume from the managed data store - //@Test(expected = InvalidParameterValueException.class) + @Test(expected = InvalidParameterValueException.class) public void attachRootDiskOfManagedDataStore() throws NoSuchFieldException, IllegalAccessException { _svc.attachVolumeToVM(2L, 7L, 0L); } // Negative test - root volume can't be attached to the vm already having a root volume attached - //@Test(expected = InvalidParameterValueException.class) + @Test(expected = InvalidParameterValueException.class) public void attachRootDiskToVmHavingRootDisk() throws NoSuchFieldException, IllegalAccessException { _svc.attachVolumeToVM(4L, 6L, 0L); } // Negative test - root volume in uploaded state can't be attached - //@Test(expected = InvalidParameterValueException.class) + @Test(expected = InvalidParameterValueException.class) public void attachRootInUploadedState() throws NoSuchFieldException, IllegalAccessException { _svc.attachVolumeToVM(2L, 8L, 0L); } // Positive test - attach ROOT volume in correct state, to the vm not having root volume attached - //@Test + @Test public void attachRootVolumePositive() throws NoSuchFieldException, IllegalAccessException { thrown.expect(NullPointerException.class); _svc.attachVolumeToVM(2L, 6L, 0L); diff --git a/systemvm/patches/debian/config/etc/dnsmasq.conf.tmpl b/systemvm/patches/debian/config/etc/dnsmasq.conf.tmpl index 447e74827d6..28b598c8e8b 100644 --- a/systemvm/patches/debian/config/etc/dnsmasq.conf.tmpl +++ b/systemvm/patches/debian/config/etc/dnsmasq.conf.tmpl @@ -415,7 +415,7 @@ dhcp-option=vendor:MSFT,2,1i # this is you want to boot machines over the network and you will need # a TFTP server; either dnsmasq's built in TFTP server or an # external one. (See below for how to enable the TFTP server.) -#dhcp-boot=pxelinux.0 +dhcp-boot=pxelinux.0 # The same as above, but use custom tftp-server instead machine running dnsmasq #dhcp-boot=pxelinux,server.name,192.168.1.100 @@ -472,10 +472,10 @@ dhcp-option=vendor:MSFT,2,1i # Enable dnsmasq's built-in TFTP server -#enable-tftp +enable-tftp # Set the root directory for files available via FTP. -#tftp-root=/var/ftpd +tftp-root=/opt/tftpboot # Make the TFTP server more secure: with this set, only files owned by # the user dnsmasq is running as will be send over the net. diff --git a/systemvm/patches/debian/config/opt/cloud/bin/baremetal_snat.sh b/systemvm/patches/debian/config/opt/cloud/bin/baremetal_snat.sh index f35a16f8e96..22e56692d7d 100755 --- a/systemvm/patches/debian/config/opt/cloud/bin/baremetal_snat.sh +++ b/systemvm/patches/debian/config/opt/cloud/bin/baremetal_snat.sh @@ -21,11 +21,12 @@ set +u mgmt_nic_ip=$1 internal_server_ip=$2 +gateway_ip=$3 ip route | grep "$internal_server_ip" > /dev/null if [ $? -ne 0 ]; then - ip route add $internal_server_ip via $mgmt_nic_ip + ip route add $internal_server_ip via $gateway_ip fi iptables-save | grep -- "-A POSTROUTING -d $internal_server_ip" > /dev/null @@ -33,3 +34,21 @@ iptables-save | grep -- "-A POSTROUTING -d $internal_server_ip" > /dev/null if [ $? -ne 0 ]; then iptables -t nat -A POSTROUTING -d $internal_server_ip -j SNAT --to-source $mgmt_nic_ip fi + + +iptables-save | grep -- "-A INPUT -i eth0 -p udp -m udp --dport 69 -j ACCEPT" > /dev/null +if [ $? -ne 0 ]; then + iptables -I INPUT -i eth0 -p udp -m udp --dport 69 -j ACCEPT +fi + +iptables-save | grep -- "-A FORWARD -i eth1 -o eth0 -j ACCEPT" > /dev/null +if [ $? -ne 0 ]; then + iptables -A FORWARD -i eth1 -o eth0 -j ACCEPT +fi + +rule="-A FORWARD -d $internal_server_ip/32 -i eth0 -o eth1 -j ACCEPT" +iptables-save | grep -- "$rule" > /dev/null +if [ $? -ne 0 ]; then + iptables -I FORWARD -d $internal_server_ip/32 -i eth0 -o eth1 -j ACCEPT +fi + diff --git a/systemvm/patches/debian/config/opt/cloud/bin/prepare_pxe.sh b/systemvm/patches/debian/config/opt/cloud/bin/prepare_pxe.sh index bd9ece69f7a..5bc1a9380dc 100755 --- a/systemvm/patches/debian/config/opt/cloud/bin/prepare_pxe.sh +++ b/systemvm/patches/debian/config/opt/cloud/bin/prepare_pxe.sh @@ -40,27 +40,9 @@ tmpt_uuid=$3 pxe_cfg_filename=$4 ks_file=$5 -tmpt_dir=$TFTP_ROOT/$tmpt_uuid -if [ -d $tmpt_dir ]; then - success -fi - -mkdir -p $tmpt_dir - kernel_path=$tmpt_uuid/$kernel_file_name initrd_path=$tmpt_uuid/$initrd_file_name -mnt_path=/tmp/$(uuid) - -mkdir -p $mnt_path -mount `dirname $kernel_nfs_path` $mnt_path -cp -f $mnt_path/$kernel_file_name $tmpt_dir/$kernel_file_name -umount $mnt_path - -mount `dirname $initrd_nfs_path` $mnt_path -cp -f $mnt_path/$initrd_file_name $tmpt_dir/$initrd_file_name -umount $mnt_path - cat > $PXELINUX_CFG_DIR/$pxe_cfg_filename < elements = new HashMap(); private String text; private String tag; @@ -38,6 +40,10 @@ public class XmlObject { XmlObject() { } + public void removeAllChildren() { + elements.clear(); + } + public XmlObject(String tag) { this.tag = tag; } @@ -99,8 +105,12 @@ public class XmlObject { if (e instanceof List) { return (List)e; } + List lst = new ArrayList(1); - lst.add(e); + if (e != null) { + lst.add(e); + } + return lst; } @@ -142,7 +152,8 @@ public class XmlObject { } if (!children.isEmpty() && text != null) { - throw new CloudRuntimeException(String.format("element %s cannot have both text[%s] and child elements", tag, text)); + logger.info(String.format("element %s cannot have both text[%s] and child elements, set text to null", tag, text)); + text = null; } if (!children.isEmpty()) { diff --git a/utils/src/com/cloud/utils/xmlobject/XmlObjectParser.java b/utils/src/com/cloud/utils/xmlobject/XmlObjectParser.java old mode 100644 new mode 100755 index 321af0ce9c8..f0e8ce31261 --- a/utils/src/com/cloud/utils/xmlobject/XmlObjectParser.java +++ b/utils/src/com/cloud/utils/xmlobject/XmlObjectParser.java @@ -19,6 +19,13 @@ package com.cloud.utils.xmlobject; +import com.cloud.utils.exception.CloudRuntimeException; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; @@ -26,15 +33,6 @@ import java.io.FileNotFoundException; import java.io.InputStream; import java.util.Stack; -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; - -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.DefaultHandler; - -import com.cloud.utils.exception.CloudRuntimeException; - public class XmlObjectParser { final private InputStream is; @@ -109,7 +107,11 @@ public class XmlObjectParser { public static XmlObject parseFromString(String xmlString) { InputStream stream = new ByteArrayInputStream(xmlString.getBytes()); XmlObjectParser p = new XmlObjectParser(stream); - return p.parse(); + XmlObject obj = p.parse(); + if (obj.getText() != null && obj.getText().replaceAll("\\n", "").replaceAll("\\r", "").replaceAll(" ", "").isEmpty()) { + obj.setText(null); + } + return obj; } private XmlObject parse() { diff --git a/utils/test/com/cloud/utils/xmlobject/TestXmlObject.java b/utils/test/com/cloud/utils/xmlobject/TestXmlObject.java old mode 100644 new mode 100755 index cbd24b029c4..faaf9803c14 --- a/utils/test/com/cloud/utils/xmlobject/TestXmlObject.java +++ b/utils/test/com/cloud/utils/xmlobject/TestXmlObject.java @@ -43,6 +43,11 @@ public class TestXmlObject { } } */ + + XmlObject xml = new XmlObject("vlan").putElement("vlan-id", String.valueOf(19)).putElement("tagged", + new XmlObject("teng").putElement("name", "0/0") + ).putElement("shutdown", "false"); + System.out.println(xml.toString()); } }