diff --git a/tests/xmlparse-xml/replace-child-build.xml b/tests/xmlparse-xml/replace-child-build.xml
new file mode 100644
index 000000000..1bc20810e
--- /dev/null
+++ b/tests/xmlparse-xml/replace-child-build.xml
@@ -0,0 +1,64 @@
+
+ 00000000-1111-2222-3333-444444444444
+ 1
+
+ hvm
+
+
+
+
+
+
+
+
+
+
+
+ /usr/bin/test-hv
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/xmlparse-xml/replace-child-parse.xml b/tests/xmlparse-xml/replace-child-parse.xml
new file mode 100644
index 000000000..f24f3e25c
--- /dev/null
+++ b/tests/xmlparse-xml/replace-child-parse.xml
@@ -0,0 +1,63 @@
+
+ 00000000-1111-2222-3333-444444444444
+ 1
+
+ hvm
+
+
+
+
+
+
+
+
+
+
+
+ /usr/bin/test-hv
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/xmlparse.py b/tests/xmlparse.py
index 4c3535764..699f8a21e 100644
--- a/tests/xmlparse.py
+++ b/tests/xmlparse.py
@@ -1451,3 +1451,31 @@ class XMLParseTest(unittest.TestCase):
raise AssertionError("Expected parse failure")
except RuntimeError as e:
self.assertTrue("'foo'" in str(e))
+
+ def testReplaceChildParse(self):
+ buildfile = "tests/xmlparse-xml/replace-child-build.xml"
+ parsefile = "tests/xmlparse-xml/replace-child-parse.xml"
+
+ def mkdisk(target):
+ disk = virtinst.DeviceDisk(self.conn)
+ disk.device = "cdrom"
+ disk.bus = "scsi"
+ disk.target = target
+ return disk
+
+ guest = virtinst.Guest(self.conn)
+ guest.add_device(mkdisk("sda"))
+ guest.add_device(mkdisk("sdb"))
+ guest.add_device(mkdisk("sdc"))
+ guest.add_device(mkdisk("sdd"))
+ guest.add_device(mkdisk("sde"))
+ guest.add_device(mkdisk("sdf"))
+ guest.devices.replace_child(guest.devices.disk[2], mkdisk("sdz"))
+ guest.set_defaults(guest)
+ utils.diff_compare(guest.get_xml(), buildfile)
+
+ guest = virtinst.Guest(self.conn, parsexml=guest.get_xml())
+ newdisk = virtinst.DeviceDisk(self.conn,
+ parsexml=mkdisk("sdw").get_xml())
+ guest.devices.replace_child(guest.devices.disk[4], newdisk)
+ utils.diff_compare(guest.get_xml(), parsefile)
diff --git a/virtinst/xmlapi.py b/virtinst/xmlapi.py
index de5dc8248..3511f7bee 100644
--- a/virtinst/xmlapi.py
+++ b/virtinst/xmlapi.py
@@ -109,6 +109,8 @@ class _XMLBase(object):
raise NotImplementedError()
def _node_remove_child(self, parentnode, childnode):
raise NotImplementedError()
+ def _node_replace_child(self, xpath, newnode):
+ raise NotImplementedError()
def _node_from_xml(self, xml):
raise NotImplementedError()
def _node_has_content(self, node):
@@ -160,6 +162,13 @@ class _XMLBase(object):
parentnode = self._node_make_stub(xpath)
self._node_add_child(xpath, parentnode, newnode)
+ def node_replace_xml(self, xpath, xml):
+ """
+ Replace the node at xpath with the passed in xml
+ """
+ newnode = self._node_from_xml(xml)
+ self._node_replace_child(xpath, newnode)
+
def node_force_remove(self, fullxpath):
"""
Remove the element referenced at the passed xpath, regardless
@@ -390,5 +399,9 @@ class _Libxml2API(_XMLBase):
parentnode.addChild(newnode)
parentnode.addChild(libxml2.newText(endtext))
+ def _node_replace_child(self, xpath, newnode):
+ oldnode = self._find(xpath)
+ oldnode.replaceNode(newnode)
+
XMLAPI = _Libxml2API
diff --git a/virtinst/xmlbuilder.py b/virtinst/xmlbuilder.py
index e878f7e8e..8e55a63c7 100644
--- a/virtinst/xmlbuilder.py
+++ b/virtinst/xmlbuilder.py
@@ -709,6 +709,24 @@ class XMLBuilder(object):
self._xmlstate.xmlapi.node_force_remove(xpath)
self._set_child_xpaths()
+ def replace_child(self, origobj, newobj):
+ """
+ Replace the origobj child with the newobj. For is_build, this
+ replaces the objects, but for !is_build this only replaces the
+ XML and keeps the object references in place. This is hacky and
+ it's fixable but at time or writing it doesn't matter for
+ our usecases.
+ """
+ if not self._xmlstate.is_build:
+ xpath = origobj.get_xml_id()
+ indent = 2 * xpath.count("/")
+ xml = util.xml_indent(newobj.get_xml(), indent).strip()
+ self._xmlstate.xmlapi.node_replace_xml(xpath, xml)
+ else:
+ origidx = origobj.get_xml_idx()
+ self.remove_child(origobj)
+ self.add_child(newobj, idx=origidx)
+
def _prop_is_unset(self, propname):
"""
Return True if the property name has never had a value set