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