Skip to content

Commit c5d6b2f

Browse files
committed
Use forced true to unmanage instance with config drive
1 parent 9604964 commit c5d6b2f

File tree

8 files changed

+493
-24
lines changed

8 files changed

+493
-24
lines changed

api/src/main/java/org/apache/cloudstack/api/command/admin/vm/UnmanageVMInstanceCmd.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.apache.cloudstack.api.response.UserVmResponse;
4343
import org.apache.cloudstack.context.CallContext;
4444
import org.apache.cloudstack.vm.UnmanagedVMsManager;
45+
import org.apache.commons.lang3.BooleanUtils;
4546

4647
import javax.inject.Inject;
4748

@@ -74,6 +75,13 @@ public class UnmanageVMInstanceCmd extends BaseAsyncCmd {
7475
since = "4.22.0")
7576
private Long hostId;
7677

78+
@Parameter(name = ApiConstants.FORCED,
79+
type = CommandType.BOOLEAN,
80+
required = false,
81+
description = "Force unmanaging Instance with config drive. Applicable only for KVM Hypervisor.",
82+
since = "4.22.0")
83+
private Boolean forced;
84+
7785
/////////////////////////////////////////////////////
7886
/////////////////// Accessors ///////////////////////
7987
/////////////////////////////////////////////////////
@@ -100,6 +108,10 @@ public void setHostId(Long hostId) {
100108
this.hostId = hostId;
101109
}
102110

111+
public Boolean isForced() {
112+
return BooleanUtils.isTrue(forced);
113+
}
114+
103115
/////////////////////////////////////////////////////
104116
/////////////// API Implementation///////////////////
105117
/////////////////////////////////////////////////////
@@ -110,7 +122,7 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE
110122
UnmanageVMInstanceResponse response = new UnmanageVMInstanceResponse();
111123
try {
112124
CallContext.current().setEventDetails("VM ID = " + vmId);
113-
Pair<Boolean, String> result = unmanagedVMsManager.unmanageVMInstance(vmId, hostId);
125+
Pair<Boolean, String> result = unmanagedVMsManager.unmanageVMInstance(vmId, hostId, isForced());
114126
if (result.first()) {
115127
response.setSuccess(true);
116128
response.setHostId(result.second());

api/src/main/java/org/apache/cloudstack/vm/UnmanageVMService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,5 @@ public interface UnmanageVMService {
2626
*
2727
* @return (true if successful, false if not, hostUuid) if the VM is successfully unmanaged.
2828
*/
29-
Pair<Boolean, String> unmanageVMInstance(long vmId, Long paramHostId);
29+
Pair<Boolean, String> unmanageVMInstance(long vmId, Long paramHostId, boolean isForced);
3030
}

core/src/main/java/com/cloud/agent/api/UnmanageInstanceCommand.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public class UnmanageInstanceCommand extends Command {
2727
String instanceName;
2828
boolean executeInSequence = false;
2929
VirtualMachineTO vm;
30+
boolean isConfigDriveAttached;
3031

3132
@Override
3233
public boolean executeInSequence() {
@@ -49,4 +50,12 @@ public String getInstanceName() {
4950
public VirtualMachineTO getVm() {
5051
return vm;
5152
}
53+
54+
public boolean isConfigDriveAttached() {
55+
return isConfigDriveAttached;
56+
}
57+
58+
public void setConfigDriveAttached(boolean configDriveAttached) {
59+
isConfigDriveAttached = configDriveAttached;
60+
}
5261
}

engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2089,6 +2089,7 @@ Long persistDomainForKVM(VMInstanceVO vm, Long paramHostId) {
20892089
unmanageInstanceCommand = new UnmanageInstanceCommand(prepVmSpecForUnmanageCmd(vm.getId(), agentHostId)); // reconstruct vmSpec for stopped instance
20902090
} else {
20912091
unmanageInstanceCommand = new UnmanageInstanceCommand(vmName);
2092+
unmanageInstanceCommand.setConfigDriveAttached(vmInstanceDetailsDao.findDetail(vm.getId(), VmDetailConstants.CONFIG_DRIVE_LOCATION) != null);
20922093
}
20932094

20942095
logger.debug("Selected host ID: {} to persist domain XML for Instance: {}.", agentHostId, vmName);

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtUnmanageInstanceCommandWrapper.java

Lines changed: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,34 @@
1919

2020
package com.cloud.hypervisor.kvm.resource.wrapper;
2121

22+
import java.io.IOException;
23+
import java.io.InputStream;
24+
import java.io.StringWriter;
2225
import java.net.URISyntaxException;
26+
import java.nio.charset.StandardCharsets;
2327

28+
import javax.xml.parsers.DocumentBuilder;
29+
import javax.xml.parsers.DocumentBuilderFactory;
30+
import javax.xml.parsers.ParserConfigurationException;
31+
import javax.xml.transform.Transformer;
32+
import javax.xml.transform.TransformerException;
33+
import javax.xml.transform.TransformerFactory;
34+
import javax.xml.transform.dom.DOMSource;
35+
import javax.xml.transform.stream.StreamResult;
36+
import javax.xml.xpath.XPath;
37+
import javax.xml.xpath.XPathConstants;
38+
import javax.xml.xpath.XPathExpressionException;
39+
import javax.xml.xpath.XPathFactory;
40+
41+
import org.apache.cloudstack.utils.security.ParserUtils;
42+
import org.apache.commons.io.IOUtils;
2443
import org.libvirt.Connect;
2544
import org.libvirt.Domain;
2645
import org.libvirt.LibvirtException;
46+
import org.w3c.dom.Document;
47+
import org.w3c.dom.Node;
48+
import org.w3c.dom.NodeList;
49+
import org.xml.sax.SAXException;
2750

2851
import com.cloud.agent.api.Answer;
2952
import com.cloud.agent.api.UnmanageInstanceAnswer;
@@ -48,29 +71,39 @@ public Answer execute(final UnmanageInstanceCommand command, final LibvirtComput
4871
logger.debug("Attempting to unmanage KVM instance: {}", instanceName);
4972
Domain dom = null;
5073
Connect conn = null;
74+
String vmFinalSpecification;
5175
try {
5276
if (vmSpec == null) {
5377
conn = libvirtUtilitiesHelper.getConnectionByVmName(instanceName);
5478
dom = conn.domainLookupByName(instanceName);
55-
String domainXML = dom.getXMLDesc(1);
56-
conn.domainDefineXML(domainXML).free();
79+
vmFinalSpecification = dom.getXMLDesc(1);
80+
if (command.isConfigDriveAttached()) {
81+
vmFinalSpecification = cleanupConfigDrive(vmFinalSpecification, instanceName);
82+
}
5783
} else {
5884
// define domain using reconstructed vmSpec
5985
logger.debug("Unmanaging Stopped KVM instance: {}", instanceName);
6086
LibvirtVMDef vm = libvirtComputingResource.createVMFromSpec(vmSpec);
6187
libvirtComputingResource.createVbd(conn, vmSpec, instanceName, vm);
6288
conn = libvirtUtilitiesHelper.getConnectionByType(vm.getHvsType());
6389
String vmInitialSpecification = vm.toString();
64-
String vmFinalSpecification = performXmlTransformHook(vmInitialSpecification, libvirtComputingResource);
65-
conn.domainDefineXML(vmFinalSpecification).free();
90+
vmFinalSpecification = performXmlTransformHook(vmInitialSpecification, libvirtComputingResource);
6691
}
67-
logger.debug("Successfully unmanaged KVM instance: {}", instanceName);
92+
conn.domainDefineXML(vmFinalSpecification).free();
93+
logger.debug("Successfully unmanaged KVM instance: {} with domain XML: {}", instanceName, vmFinalSpecification);
6894
return new UnmanageInstanceAnswer(command, true, "Successfully unmanaged");
6995
} catch (final LibvirtException e) {
70-
logger.warn("LibvirtException occurred during unmanaging instance: {} ", instanceName, e);
96+
logger.error("LibvirtException occurred during unmanaging instance: {} ", instanceName, e);
7197
return new UnmanageInstanceAnswer(command, false, e.getMessage());
72-
} catch (final URISyntaxException | InternalErrorException e) {
73-
logger.warn("URISyntaxException ", e);
98+
} catch (final IOException
99+
| ParserConfigurationException
100+
| SAXException
101+
| TransformerException
102+
| XPathExpressionException
103+
| InternalErrorException
104+
| URISyntaxException e) {
105+
106+
logger.error("Failed to unmanage Instance: {}.", instanceName, e);
74107
return new UnmanageInstanceAnswer(command, false, e.getMessage());
75108
} finally {
76109
if (dom != null) {
@@ -83,6 +116,45 @@ public Answer execute(final UnmanageInstanceCommand command, final LibvirtComput
83116
}
84117
}
85118

119+
String cleanupConfigDrive(String domainXML, String instanceName) throws ParserConfigurationException, IOException, SAXException, XPathExpressionException, TransformerException {
120+
String isoName = "/" + instanceName + ".iso";
121+
DocumentBuilderFactory docFactory = ParserUtils.getSaferDocumentBuilderFactory();
122+
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
123+
Document document;
124+
try (InputStream inputStream = IOUtils.toInputStream(domainXML, StandardCharsets.UTF_8)) {
125+
document = docBuilder.parse(inputStream);
126+
}
127+
XPathFactory xPathFactory = XPathFactory.newInstance();
128+
XPath xpath = xPathFactory.newXPath();
129+
130+
// Find all <disk device='cdrom'> elements with source file containing instanceName.iso
131+
String expression = String.format("//disk[@device='cdrom'][source/@file[contains(., '%s')]]", isoName);
132+
NodeList cdromDisks = (NodeList) xpath.evaluate(expression, document, XPathConstants.NODESET);
133+
134+
// If nothing matched, return original XML
135+
if (cdromDisks == null || cdromDisks.getLength() == 0) {
136+
logger.debug("No config drive found in domain XML for Instance: {}", instanceName);
137+
return domainXML;
138+
}
139+
140+
// Remove all matched config drive disks
141+
for (int i = 0; i < cdromDisks.getLength(); i++) {
142+
Node diskNode = cdromDisks.item(i);
143+
if (diskNode != null && diskNode.getParentNode() != null) {
144+
diskNode.getParentNode().removeChild(diskNode);
145+
}
146+
}
147+
logger.debug("Removed {} config drive ISO CD-ROM entries for instance: {}", cdromDisks.getLength(), instanceName);
148+
149+
TransformerFactory transformerFactory = ParserUtils.getSaferTransformerFactory();
150+
Transformer transformer = transformerFactory.newTransformer();
151+
DOMSource source = new DOMSource(document);
152+
StringWriter output = new StringWriter();
153+
StreamResult result = new StreamResult(output);
154+
transformer.transform(source, result);
155+
return output.toString();
156+
}
157+
86158
private String performXmlTransformHook(String vmInitialSpecification, final LibvirtComputingResource libvirtComputingResource) {
87159
String vmFinalSpecification;
88160
try {

0 commit comments

Comments
 (0)