ES STELLT SICH VOR...

Johannes Truschnigg jun.

Bild von Johannes Truschnigg, Mai 2006
  • Geburtsdatum: 28. 08. 1985
  • Herkunft: Bezirk Leoben, Österreich
  • Formale Bildung: Matura (Realgymnasium)
  • Familienstand: in zarten Händen
  • Beschäftigung: Student, Sysadmin
  • Nichtraucher: ja
  • Grundwehrdienst abgeleistet: ja
  • Führerscheininhaber: ja
  • Besondere Interessen: Freie Software, GNU/Linux, UNIX-artige Systeme, Skriptprogrammiersprachen, Systemoptimierung

Zur Suche ▼Zum Archiv ▼

Eintrag von 2010-02-01

DSM/TRIM auch für Kingston-SSDs mit Intel-Controller


Über Solid State Disks habe ich erst kürzlich in meinem Blog geschrieben - ich selbst hatte mir vor einigen Wochen ein solches Gerät von Kingston, genauer ein Kingston SSDNow V-Series 40GB, angeschafft. Im Inneren des Geräts werkt ein SSD-Controller von Intel, der zu den besten seiner Art zählt: Gegenwärtig können ihm allein Konkurrenzprodukte der Firma Indilinx das Wasser reichen.

Kingstons 40GB-SSD-Variante der Intel Postville-SSDs hat sich offenbar so gut verkauft, dass Intel dieses Geschäft von nun an lieber selbst - das zumindest ist meine Erklärung dafür, dass Kingston das Gerät nicht mehr "produziert" (eigentlich nur verkauft), und es von Intel seit kurzem ein Produkt namens Intel X25-V G2 40GB gibt, das zur Kingston V-Series baugleich sein soll.

Damit nicht genug, hat man bei Intel und/oder Kingston auch noch beschlossen, dass diese "neue" 40GB-SSD ein durchaus relevantes Feature mehr haben sollte, um es attraktiver als den Vorgänger zu machen: ATA Data Set Management, auch bekannt unter dem Kürzel TRIM. Mittels DSM/TRIM kann ein Dienstprogramm bzw. Dateisystem dem SSD-Controller mitteilen, welche Blöcke auf dem Gerät es de facto nicht mehr allokiert, worauf diese dann wieder in die Kandidatenliste für das Wear Levelling aufgenommen werden können. Dies erhöht im besten Fall die Labenesdauer eines SSD, sorgt aber auf jeden Fall für höhere Schreibgeschwindigkeiten mit zunehmendem Alter des Mediums. Während man bei Kingston anfänglich auf Nachfrage noch meinte, DSM/TRIM für die SSD per Firmware-Update nachzuliefern, gab es vor einiger Zeit einen kleinen Aufschrei in der Netzgemeinde: Seitens Kingston wurde beiläufig bekanntgegeben, dass dieses Feature für diese Produktreihe nicht (mehr) auf der Roadmap stünde - es wären keine Firmware-Updates für die Kingston V-Series mehr zu erwarten.

Dazu sollte man wissen: Die Kingston V-Series SSD verfügt im Auslieferungszustand über die Firmware in Version 02HA - dies ist die erste Version der Firmware für Intel Postville SSDs, die DSM/TRIM aktiviert. Es ist zugleich auch jene Version, die von Intel nach wenigen Stunden wieder aus dem Downloadbereich zurückgezogen wurde, weil sie bei einigen Nutzern unter Windows 7 zu nicht näher beschriebenen Problemen geführt haben soll. Einige Zeit später erschien die fehlerkorrigierte Firmware-Version 02HD, die auch heute noch aktuell ist, und DSM/TRIM problemlos zur Verfügung stellt. Ohne Einblick in den Firmware-Quellcode konnte man also als Kingston-Kunde nicht sicher sein, ob dieser potenziell Datenverlust verursachende Fehler nicht auch in der Firmware der eigenen SSD schlummert - und eine Behebung dieses beunruhigenden Umstandes war nicht abzusehen.

Zum Updaten der Firmware von Intel-SSDs liefert Intel das Image einer FreeDOS-Boot-CD mit dem Programm iSSDFUT.exe. Lässt man dieses mit einer Kingston-SSD im System ablaufen meldet es nach wenigen Sekunden, dass keine Intel-SSD gefunden wurde, die für ein Firmware-Update in Frage käme. Gleich nachdem ich dies mit eigenen Augen beobachten durfte, habe ich mich an das Aushebeln des vermuteten Sperrmechanismus gesetzt. Nach einigen kleinen Modifikationen an der DOS-Binary hatte ich das Update-Programm zumindest so weit gebracht, dass mein SSD - trotz des Kingston-Brandzeichens in der Gerätekennung - in der Liste der erkannten SSDs aufschien. Das Update wurde mir dennoch verweigert: meine Firmware wäre eine "pre-production Version", und ich solle mich doch bitte an den Intel-Support wenden. In Folge ist es mir nicht gleich gelungen, auch diesen Mechanismus zu umgehen - was nicht mehr weiter tragisch ist, da ein overclock.net-User Namens telnet inzwischen ein von meinem Hack inspiriertes, voll funktionstüchtiges Update-Programm veröffentlicht hat. Mit diesem ist es möglich, die Firmware-Version 02HD auch auf ein Kingston-SSD aufzuspielen. Dies ist ein nicht ungefährlicher Prozess, der das Gerät möglicherweise irreparabel beschädigen kann - ich wurde zum Glück vor solchen Problemen verschont.

Ernüchternd allerdings nach dem Flashen die Erkenntnis, dass trotz der aktualisierten Firmware immer noch kein DSM/TRIM im Feature-Set der SSD aufscheinen wollte. Weitere Nachforschungen des Urhebers des modifizierten Update-Programms ergaben, dass ein bestimmtes ATA-Kommando in Richtung des Kingston-Geräts abgesetzt werden muss, um es nun tatsächlich zu aktivieren: dco restore (drive configuration restore). Das Programm `hdparm` erledigt das unter GNU/Linux (hier Beispielhaft am Device sda):

# hdparm --yes-i-know-what-i-am-doing --dco-restore /dev/sda

hdparm warnt, dass diese Operation "EXTREMELY DANGEROUS" ist, und mit hoher Wahrscheinlichkeit zu Datenverlust führen wird. Mit einem Backup gerüstet habe ich es dennoch gewagt, und keine der befürchteten Probleme bekommen. Gemeinsam mit dem Script `wiper.sh` (leider ist eine gepatchte Version für Intel-SSD-Controller notwendig) aus dem hdparm-Distributionstarball und hdparm 9.27 ist es mir nun möglich, DSM/TRIM zur Leistungssteigerung meines SSD zu nutzen. Ab Linux 2.6.33 und unter Nutzung von btrfs oder ext4 gibt es zudem bald automatisches TRIM im Hintergrund, sodass die manuelle Anwendung von `wiper.sh` nicht mehr notwendig ist.

direkter Link ▲

Eintrag von 2009-11-22

Solid State Disks - Hype oder Muss?


Solid State Disks (auch Solid State Drives, kurz SSDs) sind schon seit geraumer Zeit in aller Munde. Während nämlich Prozessoren, Caches, Hauptspeicher und sogar Netzwerkanbindungen in den letzten Jahrezehnten um Größenordnungen schneller geworden sind, hat sich bei rotierenden, magnetischen und/oder optischen Medien vergleichsweise wenig getan. SSDs brechen nun endlich mit dieser lange währenden Tradition.

Aktuelle Festplatten lesen und schreiben sequentiell über 100MiB/s, und die mittlere Zugriffszeit bewegt sich - wenn man von in der Realität erreichten Werten ausgeht, die so nicht unbedingt in den Hersteller-Datenblättern auftauchen - im Bereich um 10ms. Das klingt nach viel bzw. wenig, aber vor allem der zweite Wert, die vergleichsweise lange mittlere Zugriffszeit, führt in der Praxis oft zu lästigen Wartepausen.

Bei einem rotierenden, magnetischen Medium wie einer Festplatte führt jeder Zugriff auf das Dateisystem zu einer Bewegung von Plattern und Leseköpfen: ein komplexes System aus Dateisystem, Platten-, Controllertreiber und Firmware übersetzt die Anforderung von Dateien in das Auslesen physikalischer Positionen auf den Magnetscheiben des Datenträgers. Über viele Jahre hat man tausende Mannstunden in Algorithmen und Kniffe zur effizienten Allokation von physikalisch günstig zueinander gelegenen Blocks schon beim Schreiben investiert, um die so notwendigen Bewegungen auf ein Minimum zu reduzieren, und damit hohe Arbeitsgeschwindigkeit zu gewährleisten. Nicht alle dieser Strategien sind für bestimmte Workloads geeignet: in einem Fileserver etwa will man hohen maximalen Durchsatz, und ein paar Millisekunden mehr Latenz nimmt man dafür gerne in Kauf. Ein großer Mailserver mit zigtausenden Mails in einem Spool-Verzeichnis kann sich solche Verzögerungen nicht leisten, und der Kompromiss kippt zugunsten von mehr, aber kleineren Lesevorgängen pro Zeiteinheit. Durch geschickte Wahl von Dateisystem, Mount-Optionen und IO-Scheduler kann man hier erstaunliche Leistungssteigerungen erzielen.

Besonders fällt oftmals die mittlere Zugriffsgeschwindigkeit ins Gewicht: wenn viele Dateien in einem Verzeichnis existieren, so wird i. A. für jede dieser Dateien mindestens ein eigener Lesezugriff getätigt, wenn diese z. B. gelistet (`ls -l` oder opendir(3)/readdir(3)) werden. Wenn man von 1.000 Dateien in einem Verzeichnis und einer mittleren Zugriffszeit von 10ms ausgeht, so dauert diese Operation rund 10.000ms, also geschlagene zehn Sekunden. Mein Home Directory beinhaltet momentan knapp unter 690 Inodes, hier fällt diese Rechnung also schon recht deutlich ins Gewicht. Dass es in der Realität trotzdem nicht jedes Mal so lange dauert, bis Konqueror mir den Inhalt dieses Verzeichnisses präsentiert, liegt am aggressiven Filesystem-Caching des Linux-Kernels. Viele Charakteristika der Dateien in einem Dateisystem hält der Kernel im Hauptspeicher das Systems vor.

Vor allem hier (und ganz besonders ohne entsprechende Cache-Inhalte) kommt eine spezifische Eigenschaft von SSDs ins Spiel: Aufgrund ihres inneren Aufbaus (NAND-Bausteine anstatt magentischer Platter) kommen sie ohne bewegliche Teile aus, und die mittlere Zugriffszeit dieser Bauelemente liegt nicht wie bei Festplatten im Milli-, sondern im Mikro- bis Nanosekundenbereich. Populäre Programme zum Benchmarken von Blockgeräten sind meist noch nicht auf diese neue, für sie "unerwartete" Größenordnung vorbereitet, und weisen deshalb einfach 0.1ms als mittlere Zugriffszeit für SSDs aus. Selbst wenn man von diesen ziemlich grob approximierten 100 Mikrosekunden ausgeht, würde das die Laufzeit aus dem obigen Beispiel von 10 Sekunden auf 0.1 Sekunden reduzieren, das Listen braucht also um den Faktor 100 weniger Zeit. Die verkürzte Wartezeit ist subjektiv kaum wahrnehmbar - ein deutlicher Gewinn gegenüber der ursprünglichen, zehnsekündigen Zwangspause im Arbeitsfluss.

Aktuelle SSD-Generationen haben mittlerweile auch prinzipbedingte Probleme wie langsame sequentielle oder wahlfreie Schreibzugriffe sehr gut im Griff, und mit dem ATA-T13-Standard TRIM ist demnächst auch für gleichbleibend hohe Performance über die gesamte Lebensdauer einer SSD gesorgt.

Ich persönlich habe trotz dieser rosigen Aussichten erst einmal klein angefangen, und für mein treues Lenovo Thinkpad x200 ein relativ günstiges 40GB Kingston-SSD mit empfehlenswertem Intel-Controller angeschafft: Ein Kingston SSDNow! SNV125-S2. Auch wenn diese nicht ganz die Leistung anderer (mindestens doppelt so teurer) Geräte (ebenfalls mit Intel-Controller) erreicht, ist der Unterschied zu meiner alten 250GB SATA-300 Platte mit 5.400RPM enorm: Xorg startet nun knappe zehn Sekunden nach dem Einschalten des Notebooks, und auch meine X-Session ist in einem Bruchteil der Zeit geladen, die ich zuvor gewöhnt war. Selten zuvor habe ich knapp 80 Euro so gut in Hardware investiert.

Um das Optimum aus einer Solid State Disk unter GNU/Linux herauszuholen sollte man sich idealerweise schon vor dem ersten Beschreiben des Datenträgers unbedingt diesen Artikel von Ted Tso zu Gemüte führen, und für die SSDs im System den noop- oder deadline-IO-Scheduler verwenden.

direkter Link ▲

Eintrag von 2009-10-04

Mehr Durchsatz für Linux-md Software RAID5


RAID steht für Redundant Array of Independent Discs, und sorgt dank verschiedener Paritätsschemata in Industrie und Wissenschaft für hohe Verfügbarkeit von Daten. Wählt man das Schema, mit dem die Daten auf den einzelnen physikalischen Datenträgern landen, besonders klug und geschickt, kann man zusätzlich auch noch mehr Performance aus dem Storage-Backend kitzeln. Genau das versuche ich gerade für das RAID5 meines neuen Homeservers, der mir - bevor ich in den Produktivbetrieb übergehe - als Testbett für vielerlei spannende Technologien dienen soll.

Die für dieses Posting relevanten Daten der Plattform sind:

Hardware:

Software:

Die Festplatten sind vergleichsweise energiesparende Modelle, und bieten ein derzeit ungeschlagenes Preis/Leistungsverhältnis. Einzeln liest jede der Platten sequentiell am (schnelleren) Anfang der Platter ~100MiB/s, was als keinesfalls außergewöhnlich, aber doch ausreichend schnell bezeichnet werden darf. Die erste der vier Platten wurde via cfdisk so partitioniert, dass eine 1500GB fassende primäre Partition mit Kennung 0xfd darauf Platz findet. Dieses Setup habe ich mittels sfdisk auf die übrigen Platten geklont, und dort mithilfe von mdadm ein RAID5-Array eingerichichtet. Joe Nelson hat einen ziemlich guten Performance-Vergleich verschiedener RAID-Setups online gestellt, und gemäß seiner Resultate habe ich mich dazu entschieden, auf eine Chunk Size von 256k und left-symmetric Parity zu setzen. Der Superblock des Arrays ist im 0.90er Format gehalten, sodass automatisches Assembling durch den Kernel während des Bootvorgangs möglich ist. Nach dem Erstellen und Initialisieren des Arrays kam der Verbund (via dd mit verschiedenen Blockgrößen) in den ersten 100GiB auf magere rund 210MiB/s sequentiellen Lesetransfer. Mit dem RAID5-Array alleine war es natürlich auch noch nicht getan: Die Flexibilität und das Snapshotting des Logical Volume Managers (LVM) sind zwei gute Gründe, diesen in fast allen Lebenslagen einzusetzen. Allein, der Performance tat das gar nicht gut: Kaum war das RAID-Array als Physical Volume mit einer zugehörigen Volume Group nebst Logical Volume eingerichtet, sank die sequentielle Leseperformance des resultierenden (LV-)Blockgeräts auf gar nur mehr 160MiB/s. Das kann man sich natürlich nicht gefallen lassen, und so habe ich einige Optimierungsmaßnahmen gesetzt, die deutliche Verbesserungen gebracht haben. Auch wenn ich nicht mitnotiert habe, welcher Schritt welche Leistungssteigerung verursacht hat, kann die folgende Liste an Aktionen doch all jenen dienlich sein, die noch ein paar Prozent mehr Durchsatz aus ihrem md-RAID quetschen wollen:

Optimierungsschritte:

Resultate:

Nach dem Setzen dieser Maßnahmen hat sich die sequentielle Lesegeschwindigkeit des Arrays auf im Mittel knapp über 300MiB/s (fast 50% mehr) gesteigert, und auch der LVM-Penalty beträgt jetzt nicht mehr an die 25%, sondern nur noch rund 3% (von 300 auf 290MiB/s). Mit ext4 auf einem 3TiB großen Logical Volume vermindert sich die Leseperformance nicht weiter, und mit bis zu 270MiB/s sequentieller Schreibgeschwindigkeit braucht sich das Array auch hier vor Enterprise-RAID-Controllern nicht zu verstecken.

Anzumerken bleibt, dass ich ohne Rücksicht auf Verluste streng auf maximal schnellen, sequentiellen Transfer hin optimiert habe. Für meine Usage-Pattern eines Homeserver (hauptsächlich als "Datengrab" für große Mediendateien und Backups) ist das ideal, für andere (wie etwa einen Mailserver mit Maildirs in den Dateisystemen) ganz sicher nicht. Es gibt leider keine "goldene Regel" für das Einrichten eines RAID-Setups. Das beste was man tun kann ist, die beeinflussbaren Parameter so gut es geht auf die eigenen Bedürfnisse und die lokalen Gegebenheiten anzupassen.

In näherer Zukunft möchte ich mich vor allem mit dem NFSv4- und CIFS-Durchsatz auf diesem Host beschäftigen. Sollte es dazu meiner Meinung nach Interessantes zu berichten geben, werde ich es hier in meinem Blog sicher erwähnen.

direkter Link ▲

Eintrag von 2009-08-08

Gentoo: kleine Sorgen mit libvirt


Ich arbeite seit einiger Zeit an einer schlanken Gentoo-Stage4 speziell zum Verwalten von virtuellen Maschinen. Zum Einsatz kommen dabei der Virtualisierungs-Management-Daemon libvirt, sowie der in den Linux-Kernel integrierte Hypervisor KVM. libvirt bietet eine stabile API zum Ansteuern verschiedener Hypervisoren, und hat sich für mich bereits in der Vergangenheit bewährt. KVM selbst nutze ich sogar auf dem Desktop täglich, denn virtuelle Gastsysteme sind aus meinem Alltag aus verschiedensten Gründen kaum noch wegzudenken.

Unter Gentoo genießen beide (Red Hat-)Produkte gute Unterstützung: die neuesten KVM-Releases finden sich immer rasch im Gentoo Repository, auch "Portage Tree" genannt, und libvirt wird ebenfalls in diesen Hauptpaketquellen angeboten.

Wie es sich für anständige Enterprise-Software gehört, ist libvirt natürlich netzwerkfähig. Für libvirt gibt es etwa die beiden Frontends virsh und virt-manager, die sowohl lokal als auch auf anderen Hosts im Netzwerk laufende libvirtd-Instanzen steuern können. Dazu greifen die Programme lokal auf zwei UNIX-Sockets zu, um Befehle an den libvirt-Server abzusetzen, und dessen Antworten zu empfangen. Geht es daran, auf einen libvirt-Server auf einem anderen Computer im Netzwerk zuzugreifen, so kann man zwischen einer in libvirt eingebauten SASL-Authentifikationsmethode und dem weitverbreiteten SSH-Protokoll wählen. Entscheidet man sich für letztere Möglichkeit, starten virsh und virt-manager einfach einen am lokalen System installierten SSH-Client, forwarden damit ein paar Ports des Zielsystems zum lokalen Rechner, und starten auf dem Remote-Host das gute alte netcat, um die schon erwähnten UNIX-Sockets ans Netzwerk zu bringen.

Genau hier beginnen die Probleme bei Gentoo: während es nämlich auf dem lokalen System mittels virsh ein Leichtes ist, virtuelle Maschinen zu verwalten, schlägt es über das Netzwerk (mit Gentoo als Zielsystem) fehl. Das graphische Werkzeug virt-manager wirft dann kryptische Fehlermeldungen aus seinen Python-Innereien, und lässt den User ziemlich ratlos im Dunkeln tapsen. virsh hingegen schafft, wie es sich für eine anständige CLI-Anwendung gehört, sofort Klarheit:

user@some-host:~$ virsh -c "qemu+ssh://remoteuser@gentoo-libvirt-host/system" 
Connecting to uri: qemu+ssh://remoteuser@gentoo-libvirt-host/system 
nc: invalid option -- 'U' 
nc -h for help 
libvir: Remote error : socket closed unexpectedly 
error: failed to connect to the hypervisor

Die in Gentoo enthaltene netcat-Version unterstützt die von libvirt-Anwendungen vorausgesetzte Option "-U" (zum Ansprechen von lokalen UNIX-Sockets) nicht. Beim Aufruf des Programms mit der in virsh und virt-manager kodierten Parameterliste beendet sich dieses artig mit einer kurzen Usage-Meldung, die virt-manager aber unterschlägt. Gentoo bietet im Portage Tree immerhin drei Implementationen von netcat: net-analyzer/gnu-netcat, net-analyzer/netcat und net-analyzer/netcat6. Das ebuild von libvirt setzt allerdings zwingend die Installation von net-analyzer/netcat voraus, welches offensichtlich nicht alle Features bietet, die von libvirt auch benötigt werden. Auch gnu-netcat versagt mit dem Switch "-U" seinen Dienst. Nur netcat6 weiß damit etwas Sinnvolles anzufangen. Allein, im System installiert es sich nicht unter dem wohlbekannten Namen nc, sondern unter nc6 - womit virsh und virt-manager ebenfalls nichts anfangen können. Dieses Problem lässt sich allerdings durch einen entsprechenden Symlink (cd /usr/bin && ln -s nc6 nc) schnell beheben.

Eine dauerhafte Lösung kann das freilich nicht sein. Bei Gentoo wird man sich überlegen müssen, ob man das vorhandene netcat-ebuild austauscht, sodass es eine taugliche Version des Programms installiert, oder doch lieber gleich auf netcat6 umsteigen möchte. Dann muss aber natürlich auch der Name der aus dem ebuild resultierenden Binary entsprechend geändert werden. Bis sich dieses Problem ausgewachsen hat, werden sich wohl noch einige User an Gentoos Version des "networking Swiss-army knife" unangenehm schneiden.

direkter Link ▲

Eintrag von 2009-06-01

Kurztipp: X11-Abstürze durch ACPI-Events entschärfen


Seit den späten 1980ern leistet das X11 Window System auf UNIX-Workstations treu seine Dienste. Auch von modernen GNU/Linux-Desktops ist es in Form der X.org-Distribution nicht wegzudenken. Und obwohl schon viele Jahre Entwicklungszeit in diese Software geflossen sind, stürzt sie doch auch gelegentlich ab.

Programmabstürze sind bei modernen Protected Mode-Betriebsystemen meist keine besonders schwerwiegende Sache: Die sich fehlerhaft verhaltenden Prozessabbilder werden vom Kernel auf Zuruf gnadenlos gekillt, und der Rest des System läuft von diesem Zwischenfall relativ unbeeindruckt weiter. Nicht ganz so beim X-Server: dieser bindet nämlich in der Regel alle Eingabegeräte an sich, wodurch weitere Benutzereingaben (wie zum Beispiel das manuelle Killen des X-Servers auf einem Text-Terminal) unmöglich gemacht werden. Der Linux-Kernel lässt eine Lösung dieses Dilemmas mithilfe des Magic SysRequest Keys, der hier vielleicht auch irgendwann einmal Thema sein wird, mit der Holzhammermethode zu. Mit etwas Vorbereitung auf diesen unerwünschten Ernstfall gibt es aber auch einen eleganteren Ausweg: Über ACPI-Events.

ACPI ist unter anderem dafür verantwortlich, dass moderne Betriebssysteme behutsam herunterfahren (oder z. B. einen Logout-Dialog einblenden), sobald man den Power-Button am eingeschalteten Rechner drückt. Unter GNU/Linux ist für diese Funktionalität bisher zumeist das Programm acpid verantwortlich, welches sich leicht an eigene Bedürfnisse anpassen lässt. In der Standardkonfiguration ruft es, sobald ein "button power"-Event registriert wird, /sbin/init 0 auf - das System wechselt in den Runlevel 0, in dem es vollständig herunterfährt. Mit einem kleinen Eingriff kann man das tonangebende Skript in /etc/acpi/default.sh so umstricken, dass beim ersten Betätigen des Powerbuttons auf das erste virtuelle Terminal gewechselt wird, und erst das zweite Mal Drücken den Shutdown auslöst. Dazu muss man lediglich den unter "power)" aufgeführten Aufruf von "/sbin/init 0" zu "/usr/local/sbin/revive.sh" umbiegen, und ein kleines Skript an ebendiese Stelle stellen - auf das Setzen des X-Bits dabei aber nicht vergessen!

Nach einem Restart des acipd-Initskripts kann man sich im Falle eines Absturzes des X-Server auch schon elegant durch einmaliges Drücken des Ausschalteknopfes behelfen. Wenn das wider Erwarten nichts bringt - und das System nicht gänzlich abgestürzt ist - schaltet ein zweiter Anschlag des Buttons ganz ab.

direkter Link ▲

© 2007-2010 Johannes Truschnigg | Design by Andreas Viklund (modified) | valid xhmtl & css

Created with free software