Zwei XML Strukturen vergleichen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
Judge
User
Beiträge: 129
Registriert: Mittwoch 13. Juni 2012, 22:27
Wohnort: Ratingen
Kontaktdaten:

Moin, moin,

ich habe in den letzten Tagen versucht mich in die Arbeit mit XML-Strukturen in Python einzulesen; dabei scheitere ich bisher leider. Ich bin mir nicht ganz sicher woran; ob die Dokus zu abstrakt sind, oder ich einfach nach den falschen Dingen suche ... auf jeden Fall komme ich nicht ganz zurecht.

Folgendes Szenario: Ich habe zwei libvirt - Hosts, welche die "Domains" (VMs) des virtualisierungs-Hosts per DRBD abgleichen; jede VM hat ein eigenes DRBD Device für seine Daten; so können wir einfach entscheiden, welche VM auf welchem der beiden Hosts liegt. Libvirt arbeitet beim Ex- und Import der Configs mit XML. Verbindung mit den libvirt-Hosts über die Python-API aufnehmen, Konfigs als XML Dateien auslesen, usw. - das ist alles kein Problem. Ich muss jetzt eigentlich nur 2 XML Dateien vergleichen. Ich habe hier mal 2 exemplarische Dateien:

Host1:

Code: Alles auswählen

<domain type='kvm'>
  <name>atradis-win7prof64</name>
  <uuid>1c7cfcdd-9585-96cd-d36a-c5067b5485cf</uuid>
  <memory>4194304</memory>
  <currentMemory>4194304</currentMemory>
  <vcpu>2</vcpu>
  <os>
    <type arch='x86_64' machine='pc-0.14'>hvm</type>
    <boot dev='hd'/>
    <boot dev='cdrom'/>
  </os>
  <features>
    <acpi/>
    <apic/>
    <pae/>
  </features>
  <cpu>
    <topology sockets='2' cores='2' threads='1'/>
  </cpu>
  <clock offset='localtime'/>
  <on_poweroff>destroy</on_poweroff>
  <on_reboot>restart</on_reboot>
  <on_crash>restart</on_crash>
  <devices>
    <emulator>/usr/bin/qemu-kvm</emulator>
    <disk type='file' device='cdrom'>
      <driver name='qemu' type='raw'/>
      <target dev='hdc' bus='ide'/>
      <readonly/>
      <address type='drive' controller='0' bus='1' unit='0'/>
    </disk>
    <disk type='block' device='disk'>
      <driver name='qemu' type='raw' cache='none'/>
      <source dev='/dev/drbd/by-res/atradis-win7'/>
      <target dev='vdb' bus='virtio'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/>
    </disk>
    <controller type='ide' index='0'>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/>
    </controller>
    <interface type='bridge'>
      <mac address='52:54:00:c1:68:ce'/>
      <source bridge='br1'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
    </interface>
    <serial type='pty'>
      <target port='0'/>
    </serial>
    <console type='pty'>
      <target type='serial' port='0'/>
    </console>
    <input type='tablet' bus='usb'/>
    <input type='mouse' bus='ps2'/>
    <graphics type='vnc' port='-1' autoport='yes'/>
    <video>
      <model type='vga' vram='9216' heads='1'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
    </video>
    <memballoon model='virtio'>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
    </memballoon>
  </devices>
</domain>
Host 2:

Code: Alles auswählen

<domain type='kvm' id='20'>
  <name>atradis-win7prof64</name>
  <uuid>1c7cfcdd-9585-96cd-d36a-c5067b5485cf</uuid>
  <memory>4194304</memory>
  <currentMemory>4194304</currentMemory>
  <vcpu>2</vcpu>
  <os>
    <type arch='x86_64' machine='pc-0.14'>hvm</type>
    <boot dev='hd'/>
    <boot dev='cdrom'/>
  </os>
  <features>
    <acpi/>
    <apic/>
    <pae/>
  </features>
  <cpu>
    <topology sockets='2' cores='2' threads='1'/>
  </cpu>
  <clock offset='localtime'/>
  <on_poweroff>destroy</on_poweroff>
  <on_reboot>restart</on_reboot>
  <on_crash>restart</on_crash>
  <devices>
    <emulator>/usr/bin/qemu-kvm</emulator>
    <disk type='file' device='cdrom'>
      <driver name='qemu' type='raw'/>
      <target dev='hdc' bus='ide'/>
      <readonly/>
      <alias name='ide0-1-0'/>
      <address type='drive' controller='0' bus='1' unit='0'/>
    </disk>
    <disk type='block' device='disk'>
      <driver name='qemu' type='raw' cache='none'/>
      <source dev='/dev/drbd/by-res/atradis-win7'/>
      <target dev='vdb' bus='virtio'/>
      <alias name='virtio-disk1'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/>
    </disk>
    <controller type='ide' index='0'>
      <alias name='ide0'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/>
    </controller>
    <interface type='bridge'>
      <mac address='52:54:00:c1:68:ce'/>
      <source bridge='br1'/>
      <target dev='vnet2'/>
      <alias name='net0'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
    </interface>
    <serial type='pty'>
      <source path='/dev/pts/4'/>
      <target port='0'/>
      <alias name='serial0'/>
    </serial>
    <console type='pty' tty='/dev/pts/4'>
      <source path='/dev/pts/4'/>
      <target type='serial' port='0'/>
      <alias name='serial0'/>
    </console>
    <input type='tablet' bus='usb'>
      <alias name='input0'/>
    </input>
    <input type='mouse' bus='ps2'/>
    <graphics type='vnc' port='5902' autoport='yes'/>
    <video>
      <model type='vga' vram='9216' heads='1'/>
      <alias name='video0'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
    </video>
    <memballoon model='virtio'>
      <alias name='balloon0'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
    </memballoon>
  </devices>
</domain>
Problematisch sind die VM Konfigurationen: Diese müssten derzeit auf beiden Hosts gesetzt werden, damit im Falle eines Host-Ausfalles die VM auf dem anderen Hosts mit der gleichen Konfiguration gestartet wird:
  • abweichende RAM-Zuweisungen
  • fehlende oder falsch zugewiesene Netzwerkbridges
  • MAC Adressen der Netzwerkkarten
  • Boot-Medien Reihenfolge, ...
... das sind wichtige Elemente, die ich gerne alarmieren würde, wenn diese sich unterscheiden.

Es gibt jedoch auch Unterschiede, die mir vollkommen egal sind: z.B.:
  • die Reihenfolge in der die Elemente gelistet sind
  • die alias-Bezeichnung einer Festplatte
  • das "on_crash" oder "on_reboot" Verhalten
  • das "id" Attribut des domain - Elemetes
Ich habe hierzu ein relativ interessant klingendes PyPi - Modul gefunden (https://pypi.python.org/pypi/xmldiff), jedoch gegen 0 gehende Doku dazu. PS: Ich vergas zu erwähnen, das ich Python3 verwende; das Modul scheint es nur für Python2 zu geben und ist daher eigentlich quasi raus, denke ich ...
Dann habe ich versucht mich in das etree submodul von xml/lxml einzuarbeiten, empfinde es aber als ultra kompliziert, sich damit zurecht zu finden...

Kann mir hier zufällig jemand einen Shortcut verraten oder zumindest sagen, mit welchem Weg ich am "richtigsten" liege?

Danke schonmal!

LG
Zuletzt geändert von Anonymous am Montag 6. Juni 2016, 10:25, insgesamt 1-mal geändert.
Grund: Quelltext in Codebox-Tags gesetzt.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@Judge: da Du ja festlegst, welche Daten übereinstimmen müssen, würde ich diese als eine Liste von X-Path-Ausdrücken speichern, die Dateien mit lxml laden und die Liste einfach für jede Datei durchgehen.
nezzcarth
User
Beiträge: 1635
Registriert: Samstag 16. April 2011, 12:47

Judge hat geschrieben: Dann habe ich versucht mich in das etree submodul von xml/lxml einzuarbeiten, empfinde es aber als ultra kompliziert, sich damit zurecht zu finden...
Ging mir mit lxml ähnlich :) Es kann manchmal etwas schwierig sein, sich nicht in den verschachtelten Datenstrukturen zu verlieren. Das liegt aber in der Natur von XML und nicht an lxml. An sich Meiner Ansicht nach wäre das jedenfalls ein vernünftiger Ansatz, kombiniert mit dem Vorschlag von Sirius 3. Soweit scheint das bei einem allerersten, auf die Schnelle zusammengebastelten "Proof-of-Concept" auch zu funktionieren (für die tatsächliche Anwendung müsste das natürlich anders machen):

Code: Alles auswählen

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from lxml import etree
from itertools import combinations


CONDITIONS = ('/domain/devices/graphics/@port',
              '/domain/devices/video/alias/@name',
              '/domain/devices/disk/@type',
              '/domain/devices/input/@bus'
            )

def main():
    with open('1.xml') as f, open('2.xml') as g:
        trees = [etree.parse(file_handle) for file_handle in (f, g)]
        for condition in CONDITIONS:
            results = [tree.xpath(condition) for tree in trees]
            if not all(i == j for i, j in combinations(results, 2)):
                print(results)
        

if __name__ == '__main__':
    main()

Nebenbei:
Käme für dich auch so etwas infrage?

(http://www.ofb.net/~egnor/xml2/)

Code: Alles auswählen

diff <(xml2 <1.xml) <(xml2 <2.xml)
1a2
> /domain/@id=20
30a32
> /domain/devices/disk/alias/@name=ide0-1-0
43a46
> /domain/devices/disk/alias/@name=virtio-disk1
50a54
> /domain/devices/controller/alias/@name=ide0
58a63,64
> /domain/devices/interface/target/@dev=vnet2
> /domain/devices/interface/alias/@name=net0
64a71
> /domain/devices/serial/source/@path=/dev/pts/4
65a73
> /domain/devices/serial/alias/@name=serial0
66a75,76
> /domain/devices/console/@tty=/dev/pts/4
> /domain/devices/console/source/@path=/dev/pts/4
68a79
> /domain/devices/console/alias/@name=serial0
70a82
> /domain/devices/input/alias/@name=input0
75c87
< /domain/devices/graphics/@port=-1
---
> /domain/devices/graphics/@port=5902
79a92
> /domain/devices/video/alias/@name=video0
85a99
> /domain/devices/memballoon/alias/@name=balloon0
Benutzeravatar
Judge
User
Beiträge: 129
Registriert: Mittwoch 13. Juni 2012, 22:27
Wohnort: Ratingen
Kontaktdaten:

nezzcarth hat geschrieben:
Judge hat geschrieben: Dann habe ich versucht mich in das etree submodul von xml/lxml einzuarbeiten, empfinde es aber als ultra kompliziert, sich damit zurecht zu finden...
Ging mir mit lxml ähnlich :) Es kann manchmal etwas schwierig sein, sich nicht in den verschachtelten Datenstrukturen zu verlieren. Das liegt aber in der Natur von XML und nicht an lxml. An sich Meiner Ansicht nach wäre das jedenfalls ein vernünftiger Ansatz, kombiniert mit dem Vorschlag von Sirius 3. Soweit scheint das bei einem allerersten, auf die Schnelle zusammengebastelten "Proof-of-Concept" auch zu funktionieren (für die tatsächliche Anwendung müsste das natürlich anders machen):

CODE ...
Das ist ein sehr hilfreicher Ansatz, der mir sehr geholfen hat meine anfänglichen Schwierigkeiten mit lxml zu überwinden. Die Liste wird so halt recht groß ... aber sei's drum, ist halt so. Ich sehe eh keinen anderen Ansatz, leider ...
Schade das man mit libvirt nicht einfach ein Änderungsdatum der Configs auslesen kann; dann müsste man garnicht vergleichen, sondern nur prüfen, ob die Änderungsdaten der beiden Host-Configs zeitlich mehr als X auseinander liegen und dann die neuere auf den mit dem älteren Datum übernehmen.

Ich glaube so komme ich erstmal weiter - danke an alle! :)
Antworten