diff --git a/etc/missing_strings.pl b/etc/missing_strings.pl
new file mode 100755
index 000000000..aef0b5556
--- /dev/null
+++ b/etc/missing_strings.pl
@@ -0,0 +1,76 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+use Data::Dumper;
+
+my $DIR = "lib/Ravada/I18N";
+
+my %LIST = map { $_ => 1 } @ARGV;
+
+sub selected {
+ my $file = shift;
+ my ($name) = $file =~ m{(.*)\.\w+};
+
+ return 0 if !exists $LIST{$name};
+ return $LIST{$name};
+}
+
+sub load_strings {
+ my $file = shift;
+
+ if ($file !~ m{/}) {
+ $file = "$DIR/$file";
+ }
+ open my $in,"<",$file or die "$! $file";
+
+ my $msgid;
+ my %found;
+ my $string;
+ while (my $line = <$in>) {
+ my ($msgstr) = $line =~ /^msgstr/;
+ if ($msgstr && $string) {
+ $found{$string}++;
+ $string = undef;
+ next;
+ }
+ my ($string1) = $line =~ /^msgid "(.*)"/;
+ if (defined $string1) {
+ $string = $string1;
+ next;
+ }
+ if (!defined $string1 && defined $string) {
+ my ($string2) = $line =~ /^"(.*)"/;
+ if (defined $string2) {
+ $msgid=0;
+ $string = "$string$string2";
+ }
+ }
+ next if !$string;
+ }
+ close $in;
+ return \%found;
+}
+
+my $english = load_strings('en.po');
+my $found=0;
+
+
+opendir my $in,$DIR or die "$! $DIR";
+while (my $file = readdir $in) {
+ next if $file !~ /\.po$/;
+ next if keys %LIST && !selected($file);
+ my $path = "$DIR/$file";
+ next if !-f $path;
+ print "$path\n";
+
+ my $string = load_strings($file);
+ for my $key (sort keys %$english) {
+ next if $string->{$key};
+ print "msgid \"$key\"\n"
+ ."msgstr \"\"\n\n";
+ $found++;
+ }
+ last if $found;
+}
+closedir $in;
diff --git a/lib/Ravada/I18N/de.po b/lib/Ravada/I18N/de.po
index ab7efaae2..02ba13f6e 100644
--- a/lib/Ravada/I18N/de.po
+++ b/lib/Ravada/I18N/de.po
@@ -912,3 +912,1060 @@ msgstr "Denken Sie dran, dass Nutzer den Klone verwenden könnten"
msgid "Yes, shutwdown all the clones"
msgstr "Ja, alle Klone herunterfahren"
+
+msgid "-- choose hardware --"
+msgstr "-- Hardware auswählen --"
+
+msgid "A node with that address already exists."
+msgstr "Es existiert bereits ein Knoten mit dieser Adresse."
+
+msgid "Accept"
+msgstr "Akzeptieren"
+
+msgid "Access"
+msgstr "Zugreifen"
+
+msgid "Action"
+msgstr "Aktion"
+
+msgid "Active"
+msgstr "Aktiv"
+
+msgid "Add another user"
+msgstr "Weiteren Nutzer anlegen"
+
+msgid "Add groups"
+msgstr "Gruppe hinzufügen"
+
+msgid "Add new Display"
+msgstr "Neuen Bildschirm hinzufügen"
+
+msgid "Add new disk"
+msgstr "Neue Festplatte hinzufügen"
+
+msgid "Add to group"
+msgstr "Zur Gruppe hinzufügen"
+
+msgid "Admin users can still log in from"
+msgstr "Administratoren können sich weiterhin einloggen"
+
+msgid "Advanced options"
+msgstr "Erweiterte Optionen"
+
+msgid "All events"
+msgstr "Alle Ereignisse"
+
+msgid "All machines"
+msgstr "Alle Maschinen"
+
+msgid "Anonymous"
+msgstr "Anonym"
+
+msgid "Any remote client can access this port"
+msgstr "Jeder verbundene Client kann auf diesen Port zugreifen"
+
+msgid "Apply"
+msgstr "Anwenden"
+
+msgid "Are you sure you want to migrate"
+msgstr "Möchten Sie wirklich migrieren"
+
+msgid "Are you sure you want to prepare the base of"
+msgstr ""
+
+msgid "Are you sure you want to remove the"
+msgstr ""
+
+msgid "Are you sure you want to remove the base of"
+msgstr ""
+
+msgid "Are you sure you want to remove the node"
+msgstr "Möchten Sie diesen Knoten wirlich entfernen"
+
+msgid "Are you sure you want to remove this group ?"
+msgstr "Möchten Sie diese Gruppe wirklich entfernen?"
+
+msgid "Are you sure you want to spinoff from the base?"
+msgstr ""
+
+msgid "Assign this virtual machine to the pre-started pool"
+msgstr "Diese virtuelle Maschine einem bereits gestartetem pool zuweisen"
+
+msgid "Attachment"
+msgstr "Anhang"
+
+msgid "Autostart"
+msgstr "Autostart"
+
+msgid "BIOS"
+msgstr "BIOS"
+
+msgid "Balance"
+msgstr "Balance"
+
+msgid "Base"
+msgstr "Basis"
+
+msgid "Bases"
+msgstr ""
+
+msgid "Bookings"
+msgstr "Buchungen"
+
+msgid "Bookings require LDAP authentication."
+msgstr "Buchungen benötigen LDAP authentifizierung"
+
+msgid "Bridge"
+msgstr "Brücke"
+
+msgid "CPU Features"
+msgstr "CPU Features"
+
+msgid "CPUs"
+msgstr "CPUs"
+
+msgid "Can expose virtual machine ports."
+msgstr "Kann Ports der virtuellen Maschine freigeben"
+
+msgid "Can get a screenshot of own virtual machines."
+msgstr "Kann Bildschirmaufnahmen von eigenen virtuellen Maschinen erstellen"
+
+msgid "Can have an unlimited amount of machines started."
+msgstr "Kann eine unbegrenzte Anzahl von Maschinen starten"
+
+msgid "Can manage groups."
+msgstr "Kann Gruppen verwalten"
+
+msgid "Can reboot all virtual machines."
+msgstr "Kann alle virtuellen Maschinen neu starten"
+
+msgid "Can reboot clones own virtual machines."
+msgstr ""
+
+msgid "Can reboot own virtual machines."
+msgstr "Kann eigene virtuelle Maschinen neu starten"
+
+msgid "Can rename any virtual machine owned by the user."
+msgstr "Kann jede viruelle Maschine dieses Nutzers umbenennen"
+
+msgid "Can rename any virtual machine."
+msgstr "Kann jede virtuelle Maschine umbenennen"
+
+msgid "Can rename clones from virtual machines owned by the user."
+msgstr "Kann Klone von virtuellen Maschinen dieses Nutzers umbenennen"
+
+msgid "Can shutdown own virtual machines."
+msgstr "Kann eigene virtuelle Maschinen umbenennen"
+
+msgid "Can view groups."
+msgstr "Kann Gruppen anzeigen"
+
+msgid "Change"
+msgstr "Ändern"
+
+msgid "Changing the base of a virtual machine is potentially dangerous and can't be undone."
+msgstr "Das ändern der Grundlage einer virtuellen maschine ist potentiell gefährlich und kann nicht rückgängig gemacht werden."
+
+msgid "Check"
+msgstr "Kontrollieren"
+
+msgid "Check connection to"
+msgstr "Kontrolliere die Verbindung zu"
+
+msgid "Choose the virtualization type of the Node."
+msgstr "Virtualisierungstyp des Knoten wählen."
+
+msgid "Choose the virtualization type of the Virtual Machine."
+msgstr "Virtualisierungstyp der virtuellen Maschine wählen."
+
+msgid "Client"
+msgstr ""
+
+msgid "Client headers"
+msgstr ""
+
+msgid "Clones are volatile, so all the pool will be started."
+msgstr ""
+
+msgid "Clones created from this machine will be removed on shutdown."
+msgstr "Klone die auf dieser Maschine erstellt wurden, werden beim herunterfahren gelöscht."
+
+msgid "Close"
+msgstr "Schließen"
+
+msgid "Color"
+msgstr "Farbe"
+
+msgid "Compact"
+msgstr "Kompakt"
+
+msgid "Compact disk volumes"
+msgstr ""
+
+msgid "Configure LDAP Authentication"
+msgstr "LDAP Authentifizierung einrichten"
+
+msgid "Confirm"
+msgstr "Bestätigen"
+
+msgid "Confirm disable node"
+msgstr "Deaktivieren des Knoten bestätigen"
+
+msgid "Confirm password can not exceed 20 characters"
+msgstr "Passwort bestätigen darf nicht länger sein als 20 Zeichen"
+
+msgid "Confirm password can only contain words and numbers"
+msgstr "Passwort bestätigen darf nur aus zahlen und wörtern bestehen"
+
+msgid "Confirm password is required"
+msgstr "Passwort bestätigen wird benötigt"
+
+msgid "Confirm password must be at least 5 characters"
+msgstr "Passwort bestätigen muss mindestend 5 Zeichen lang sein"
+
+msgid "Congrats"
+msgstr "Glückwunsch"
+
+msgid "Contact Method"
+msgstr "Kontaktmethode"
+
+msgid "Content will be cleaned on restore"
+msgstr "Inhalt wird beim wiederherstellen gelöscht"
+
+msgid "Content will be cleaned on restore and shutdown"
+msgstr "Inhalt wird beim wiederherstellen und herunterfahren gelöscht"
+
+msgid "Content will be kept on restore"
+msgstr "Inhalt wird beim wiederherstellen behalten"
+
+msgid "Create a new group"
+msgstr "Neue Gruppe erstellen"
+
+msgid "Current memory (MB)"
+msgstr "Aktueller Speicher (MB)"
+
+msgid "Danger: This will destroy all the disk data permantently."
+msgstr "Achtung: Alle daten werden dieser Festplatte werden permanent gelöscht."
+
+msgid "Data"
+msgstr "Daten"
+
+msgid "Debug"
+msgstr ""
+
+msgid "Debug Ports"
+msgstr "Debug Ports"
+
+msgid "Debug Ports Exposed"
+msgstr "Debug Ports öffentlich"
+
+msgid "Delete"
+msgstr "Löschen"
+
+msgid "Delete the event"
+msgstr "Das Ereignis löschen"
+
+msgid "Disable"
+msgstr "Deaktivieren"
+
+msgid "Disabled"
+msgstr "Deaktiviert"
+
+msgid "Disabling this node will shut all the"
+msgstr ""
+
+msgid "Display"
+msgstr "Anzeige"
+
+msgid "Display IP :"
+msgstr ""
+
+msgid "Display Password"
+msgstr ""
+
+msgid "Display Port :"
+msgstr ""
+
+msgid "Display Port secure :"
+msgstr ""
+
+msgid "Display URL :"
+msgstr ""
+
+msgid "Edit the event"
+msgstr ""
+
+msgid "Email"
+msgstr "E-Mail"
+
+msgid "Enable"
+msgstr "Aktivieren"
+
+msgid "Enables display password when available"
+msgstr ""
+
+msgid "End"
+msgstr "Ende"
+
+msgid "Enter New Password"
+msgstr "Neues Passwort eigeben"
+
+msgid "Enter Old Password"
+msgstr "Alten Passwort eingeben"
+
+msgid "Enter group name"
+msgstr "Gruppenname eingeben"
+
+msgid "Error:"
+msgstr "Fehler:"
+
+msgid "Error: the LDAP entry for this user has been removed."
+msgstr "Fehler: Der LDAP Eintrag für diesen Benutzer wurde gelöscht."
+
+msgid "Error: you want to have"
+msgstr ""
+
+msgid "Event properties"
+msgstr "Ereigniseigenschaften"
+
+msgid "Event saved successfully"
+msgstr "Ereignis erfolgreich gespeichert"
+
+msgid "Exec clones sequentially"
+msgstr ""
+
+msgid "Exec. time"
+msgstr ""
+
+msgid "Fail"
+msgstr "Fehlgeschlagen"
+
+msgid "Fallback"
+msgstr "Fallback"
+
+msgid "Fill required camps with valid data"
+msgstr ""
+
+msgid "For Spice client setup follow this "
+msgstr ""
+
+msgid "Force Reboot"
+msgstr ""
+
+msgid "Force ShutDown"
+msgstr ""
+
+msgid "Force change password on first access"
+msgstr ""
+
+msgid "From"
+msgstr ""
+
+msgid "Frontend"
+msgstr ""
+
+msgid "Global Settings"
+msgstr ""
+
+msgid "Group"
+msgstr ""
+
+msgid "Group name"
+msgstr ""
+
+msgid "Group name can not exceed 80 characters"
+msgstr ""
+
+msgid "Group name is required"
+msgstr ""
+
+msgid "Groups"
+msgstr ""
+
+msgid "Groups require a LDAP server configured."
+msgstr ""
+
+msgid "Groups with permissions"
+msgstr ""
+
+msgid "Hardware"
+msgstr ""
+
+msgid "Hardware address"
+msgstr ""
+
+msgid "Hibernated"
+msgstr ""
+
+msgid "Hide active"
+msgstr ""
+
+msgid "Hide clones"
+msgstr ""
+
+msgid "I can't find"
+msgstr ""
+
+msgid "I don't know settings tabs for:"
+msgstr ""
+
+msgid "ISO file"
+msgstr ""
+
+msgid "If you have any issue, you can contact the Suport crew and we will try to help you. Please, only contact Support Center if it is strictly necessary."
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid "Internal IP"
+msgstr ""
+
+msgid "Invalid IP network address. Expecting a.b.c.d/e"
+msgstr ""
+
+msgid "Invalid Template"
+msgstr ""
+
+msgid "Keep the CD for the clones"
+msgstr ""
+
+msgid "LDAP"
+msgstr ""
+
+msgid "LDAP groups are required to set up bookings. Some groups where found but no members belong to them. Add new entries here."
+msgstr ""
+
+msgid "Legacy"
+msgstr ""
+
+msgid "Loading ..."
+msgstr ""
+
+msgid "Loading machine"
+msgstr ""
+
+msgid "Loading nodes"
+msgstr ""
+
+msgid "Machine"
+msgstr ""
+
+msgid "Machine Information"
+msgstr ""
+
+msgid "Machines Notifications"
+msgstr ""
+
+msgid "Maintenance"
+msgstr ""
+
+msgid "Maintenance End"
+msgstr ""
+
+msgid "Maintenance Start"
+msgstr ""
+
+msgid "Manage machine"
+msgstr ""
+
+msgid "Max Memory"
+msgstr ""
+
+msgid "Max memory (MB)"
+msgstr ""
+
+msgid "Minimum port number to expose virtual machine services."
+msgstr ""
+
+msgid "Monitoring"
+msgstr ""
+
+msgid "NAT"
+msgstr ""
+
+msgid "Network"
+msgstr ""
+
+msgid "Network address is required."
+msgstr ""
+
+msgid "Network name is required"
+msgstr ""
+
+msgid "Networks"
+msgstr ""
+
+msgid "New Booking"
+msgstr ""
+
+msgid "New Group"
+msgstr ""
+
+msgid "New Network"
+msgstr ""
+
+msgid "New Password can only contain words and numbers"
+msgstr ""
+
+msgid "New Password is required"
+msgstr ""
+
+msgid "New Password must be at least 6 characters"
+msgstr ""
+
+msgid "New feature"
+msgstr ""
+
+msgid "New group member"
+msgstr ""
+
+msgid "New name"
+msgstr ""
+
+msgid "New network"
+msgstr ""
+
+msgid "New password"
+msgstr ""
+
+msgid "New user"
+msgstr ""
+
+msgid "No LDAP groups created."
+msgstr ""
+
+msgid "No Results Found"
+msgstr ""
+
+msgid "No bases found"
+msgstr ""
+
+msgid "No bridges found"
+msgstr ""
+
+msgid "No groups found"
+msgstr ""
+
+msgid "No machines"
+msgstr ""
+
+msgid "No machines found"
+msgstr ""
+
+msgid "No members found"
+msgstr ""
+
+msgid "No message to show!"
+msgstr ""
+
+msgid "No, cancel"
+msgstr ""
+
+msgid "Node"
+msgstr ""
+
+msgid "Node disabled"
+msgstr ""
+
+msgid "Node down"
+msgstr ""
+
+msgid "Node with machines from the same user"
+msgstr ""
+
+msgid "Node with more free memory"
+msgstr ""
+
+msgid "Nodes"
+msgstr ""
+
+msgid "Nothing to monitoring"
+msgstr ""
+
+msgid "Number of virtual machines that normal users can have running at the same time"
+msgstr ""
+
+msgid "Old Password can only contain words and numbers"
+msgstr ""
+
+msgid "Old Password is required"
+msgstr ""
+
+msgid "Old and New Passwords match!"
+msgstr ""
+
+msgid "Only remote client can access this port"
+msgstr ""
+
+msgid "Only remote client can access this port if restricted"
+msgstr ""
+
+msgid "Only users from these groups will be allowed to execute this machine"
+msgstr ""
+
+msgid "Oops!"
+msgstr ""
+
+msgid "Open ports"
+msgstr ""
+
+msgid "Password and their confirmation do not match!"
+msgstr ""
+
+msgid "Passwords do not match!"
+msgstr ""
+
+msgid "Permission granted to user"
+msgstr ""
+
+msgid "Permission revoked from user"
+msgstr ""
+
+msgid "Phone"
+msgstr ""
+
+msgid "Phone Number"
+msgstr ""
+
+msgid "Please insert"
+msgstr ""
+
+msgid "Please select an ISO file"
+msgstr ""
+
+msgid "Please, make sure you have the right path and release, according to your PC configuration."
+msgstr ""
+
+msgid "Pool"
+msgstr ""
+
+msgid "Port"
+msgstr ""
+
+msgid "Port expose"
+msgstr ""
+
+msgid "Ports"
+msgstr ""
+
+msgid "Poweroff"
+msgstr ""
+
+msgid "Prepare Base"
+msgstr ""
+
+msgid "Prepare this machine as a base to create clones from it."
+msgstr ""
+
+msgid "PrepareBase"
+msgstr ""
+
+msgid "Press SHIFT + F12 to exit the virtual machine"
+msgstr ""
+
+msgid "Primary can not be unset, enable it in another video device"
+msgstr ""
+
+msgid "Primary video devices will move to first"
+msgstr ""
+
+msgid "Public"
+msgstr ""
+
+msgid "Public Port"
+msgstr ""
+
+msgid "Purge"
+msgstr ""
+
+msgid "Purge disk volumes"
+msgstr ""
+
+msgid "RAM (Gb)"
+msgstr ""
+
+msgid "Ravada Apache documentation"
+msgstr ""
+
+msgid "Re-Enter New Password"
+msgstr ""
+
+msgid "Rebase"
+msgstr ""
+
+msgid "Rebase all the clones to a new base"
+msgstr ""
+
+msgid "Rebase this machine to another virtual machine"
+msgstr ""
+
+msgid "Reboot"
+msgstr ""
+
+msgid "Recent requests"
+msgstr ""
+
+msgid "Refresh"
+msgstr ""
+
+msgid "Refresh ports"
+msgstr ""
+
+msgid "Reload"
+msgstr ""
+
+msgid "Remove Base"
+msgstr ""
+
+msgid "Remove Clones"
+msgstr ""
+
+msgid "Remove bases in node"
+msgstr ""
+
+msgid "Remove group"
+msgstr ""
+
+msgid "Remove the base prepared from this machine"
+msgstr ""
+
+msgid "Removing Virtual Machine"
+msgstr ""
+
+msgid "Repeat"
+msgstr ""
+
+msgid "Repeated New Password is required"
+msgstr ""
+
+msgid "Request"
+msgstr ""
+
+msgid "Requests"
+msgstr ""
+
+msgid "Requires viewer password when implemented"
+msgstr ""
+
+msgid "Restricted"
+msgstr ""
+
+msgid "Run Timeout"
+msgstr ""
+
+msgid "SSO/CAS Login"
+msgstr ""
+
+msgid "Save"
+msgstr ""
+
+msgid "Save changes"
+msgstr ""
+
+msgid "Schedule"
+msgstr ""
+
+msgid "Search group"
+msgstr ""
+
+msgid "See documentation about:"
+msgstr ""
+
+msgid "Select the .iso file the machine will utilize when installing the OS."
+msgstr ""
+
+msgid "Server in maintenance until"
+msgstr ""
+
+msgid "Set New Password"
+msgstr ""
+
+msgid "Set defaults"
+msgstr ""
+
+msgid "Set either"
+msgstr ""
+
+msgid "Setup a LDAP Server"
+msgstr ""
+
+msgid "Show active"
+msgstr ""
+
+msgid "Show clones"
+msgstr ""
+
+msgid "Show/Hide clones"
+msgstr ""
+
+msgid "ShutDown"
+msgstr ""
+
+msgid "Shutdown Timeout"
+msgstr ""
+
+msgid "Shutdown disconnected"
+msgstr ""
+
+msgid "Sorry"
+msgstr ""
+
+msgid "Source Machine"
+msgstr ""
+
+msgid "Spinoff clone"
+msgstr ""
+
+msgid "Spinoff this clone from its base."
+msgstr ""
+
+msgid "Start Limit"
+msgstr ""
+
+msgid "Start after create the virtual machine"
+msgstr ""
+
+msgid "Start after migration"
+msgstr ""
+
+msgid "Support Contact"
+msgstr ""
+
+msgid "Swap"
+msgstr ""
+
+msgid "System Disk: (GB)"
+msgstr ""
+
+msgid "Template selection is required"
+msgstr ""
+
+msgid "Testing connection to"
+msgstr ""
+
+msgid "The Minimum Disk Size needed for this ISO is"
+msgstr ""
+
+msgid "The Minimum Swap Disk Size needed for this ISO is"
+msgstr ""
+
+msgid "The VM is"
+msgstr ""
+
+msgid "The machine will power off after this minutes after shutdown."
+msgstr ""
+
+msgid "The machine will turn off after this time"
+msgstr ""
+
+msgid "The new user"
+msgstr ""
+
+msgid "The source machine is not a base. It must be prepared before it can be copied. This process may take some minutes."
+msgstr ""
+
+msgid "There are no LDAP groups defined."
+msgstr ""
+
+msgid "There are no active virtual machines"
+msgstr ""
+
+msgid "This ISO is being downloaded. The virtual machine will be created after."
+msgstr ""
+
+msgid "This Virtual Machine has no display hardware attached"
+msgstr ""
+
+msgid "This address is duplicated"
+msgstr ""
+
+msgid "This base can't be removed because the domain has clones."
+msgstr ""
+
+msgid "This base has"
+msgstr ""
+
+msgid "This base has clones"
+msgstr ""
+
+msgid "This booking overlaps already scheduled reservations"
+msgstr ""
+
+msgid "This can't be undone"
+msgstr ""
+
+msgid "This event"
+msgstr ""
+
+msgid "This event and the following"
+msgstr ""
+
+msgid "This event and the following with same day of week"
+msgstr ""
+
+msgid "This feature requires a LDAP server configured."
+msgstr ""
+
+msgid "This field is required"
+msgstr ""
+
+msgid "This group has been removed."
+msgstr ""
+
+msgid "This is a main node and can't be removed."
+msgstr ""
+
+msgid "This machine has a CD-ROM"
+msgstr ""
+
+msgid "This machine is base and can't be modified."
+msgstr ""
+
+msgid "This machine is base and it can't be modified."
+msgstr ""
+
+msgid "This machine is hibernated and can't be renamed."
+msgstr ""
+
+msgid "This machine is locked by process"
+msgstr ""
+
+msgid "This machine is running and can't be modified."
+msgstr ""
+
+msgid "This machine is running and can't be renamed."
+msgstr ""
+
+msgid "This machine will shut down"
+msgstr ""
+
+msgid "This name is duplicated"
+msgstr ""
+
+msgid "This name is invalid. It can only contain alphabetic, numbers, undercores and dashes and must start by a letter."
+msgstr ""
+
+msgid "This node has"
+msgstr ""
+
+msgid "This node is local"
+msgstr ""
+
+msgid "This server has reservations for today. Machines from users out of\n the booking list will be shutdown."
+msgstr ""
+
+msgid "This user has never logged in the Ravada server."
+msgstr ""
+
+msgid "This virtual machine has already been compacted"
+msgstr ""
+
+msgid "This virtual machine has clones that are bases and won't be removed"
+msgstr ""
+
+msgid "This virtual machine has no backups to purge"
+msgstr ""
+
+msgid "This virtual machine has no group restrictions."
+msgstr ""
+
+msgid "This virtual machine is running. It must be shut down before migrate."
+msgstr ""
+
+msgid "Time"
+msgstr ""
+
+msgid "Title"
+msgstr ""
+
+msgid "To"
+msgstr ""
+
+msgid "To make this possible, copy the content of"
+msgstr ""
+
+msgid "Today Schedule"
+msgstr ""
+
+msgid "Type"
+msgstr ""
+
+msgid "Type the ISO pathname"
+msgstr ""
+
+msgid "Type the name of the volume disk to confirm:"
+msgstr ""
+
+msgid "Type the template name"
+msgstr ""
+
+msgid "UEFI"
+msgstr ""
+
+msgid "Until"
+msgstr ""
+
+msgid "User is not member of any group."
+msgstr ""
+
+msgid "User not found"
+msgstr ""
+
+msgid "Users from this network can run all virtual machines"
+msgstr ""
+
+msgid "Users from this network can run no virtual machines"
+msgstr ""
+
+msgid "Virtual Machine will be shutdown when user disconnects."
+msgstr ""
+
+msgid "Virtual Machine will start on host start."
+msgstr ""
+
+msgid "Virtual machines in the pool."
+msgstr ""
+
+msgid "Virtual machines pre-started"
+msgstr ""
+
+msgid "Volatile"
+msgstr ""
+
+msgid "Waiting for machine to start"
+msgstr ""
+
+msgid "Waiting for network to come up"
+msgstr ""
+
+msgid "Waiting for requests to complete"
+msgstr ""
+
+msgid "Warning"
+msgstr ""
+
+msgid "Warning: anonymous machines won't show up unless you enable allowed."
+msgstr ""
+
+msgid "Warning: anonymous machines won't show up unless you set them public."
+msgstr ""
+
+msgid "Warning: this virtual machine will be prepared as a base. This may take long."
+msgstr ""
+
+msgid "Web Service connection failed."
+msgstr ""
+
+msgid "Weekly"
+msgstr ""
+
+msgid "What do you want to do now?"
+msgstr ""
+
+msgid "Will be destroyed on shutdown"
+msgstr ""
+
+msgid "Yes, rebase"
+msgstr ""
+
diff --git a/lib/Ravada/VM.pm b/lib/Ravada/VM.pm
index 09b9b87d0..606700b59 100644
--- a/lib/Ravada/VM.pm
+++ b/lib/Ravada/VM.pm
@@ -146,8 +146,6 @@ around 'ping' => \&_around_ping;
around 'connect' => \&_around_connect;
after 'disconnect' => \&_post_disconnect;
-around '_list_used_volumes' => \&_around_list_used_volumes;
-
#############################################################
#
# method modifiers
@@ -2515,7 +2513,6 @@ sub dir_backup($self) {
return $dir_backup;
}
-
sub _follow_link($self, $file) {
my ($dir, $name) = $file =~ m{(.*)/(.*)};
my $file2 = $file;
@@ -2525,13 +2522,20 @@ sub _follow_link($self, $file) {
}
if (!defined $self->{_is_link}->{$file2} ) {
- my ($out,$err) = $self->run_command("stat", $file2);
+ my ($out,$err) = $self->run_command("stat","-c",'"%N"', $file2);
chomp $out;
- $out =~ m{ -> (/.*)};
- $self->{_is_link}->{$file2} = $1;
+ my ($link) = $out =~ m{ -> '(.+)'};
+ if ($link) {
+ if ($link !~ m{^/}) {
+ my ($path) = $file2 =~ m{(.*/)};
+ $path = "/" if !$path;
+ $link = "$path$link";
+ }
+ $self->{_is_link}->{$file2} = $link;
+ }
}
- my $path = $self->{_is_link}->{$file2};
- return ($path or $file2);
+ $self->{_is_link}->{$file2} = $file2 if !exists $self->{_is_link}->{$file2};
+ return $self->{_is_link}->{$file2};
}
sub _is_link_remote($self, $vol) {
@@ -2576,12 +2580,33 @@ sub _is_link($self,$vol) {
return $path_link if $path_link;
}
-sub _around_list_used_volumes($orig, $self) {
- my @vols = $self->$orig();
- my @links;
- for my $vol ( @vols ) {
- my $link = $self->_is_link($vol);
- push @links,($link) if $link;
+=head2 list_unused_volumes
+
+Returns a list of unused volume files
+
+=cut
+
+sub list_unused_volumes($self) {
+ my @all_vols = $self->list_volumes();
+
+ my @used = $self->list_used_volumes();
+ my %used;
+ for my $vol (@used) {
+ $used{$vol}++;
+ my $link = $self->_follow_link($vol);
+ $used{$link}++ if $link;
+ }
+
+ my @vols;
+ my %duplicated;
+ for my $vol ( @all_vols ) {
+ next if $used{$vol};
+
+ my $link = $self->_follow_link($vol);
+ next if $used{$link};
+ next if $duplicated{$link}++;
+
+ push @vols,($link);
}
return @vols;
}
diff --git a/lib/Ravada/VM/KVM.pm b/lib/Ravada/VM/KVM.pm
index d9705750f..ad5eb5f70 100644
--- a/lib/Ravada/VM/KVM.pm
+++ b/lib/Ravada/VM/KVM.pm
@@ -336,6 +336,8 @@ sub search_volume_re($self,$pattern,$refresh=0) {
my @vols;
for ( 1 .. 10) {
eval { @vols = $pool->list_all_volumes() };
+ last if $@ && ref($@) && $@->code == 55;
+ last if $@ && $@ =~ /code: (55|38),/;
last if !$@ || $@ =~ / no storage pool with matching uuid/;
warn "WARNING: on search volume_re: $@";
sleep 1;
@@ -385,24 +387,16 @@ sub _list_volumes($self) {
my @pools;
my %duplicated_path;
for my $pool (_list_storage_pools($self->vm)) {
- next if !$pool->is_active;
-
- my $xml = XML::LibXML->load_xml(string => $pool->get_xml_description());
-
- my $path = $xml->findnodes('/pool/target/path/text()');
- $path = $self->_follow_link($path);
- push @pools,($pool) if !$duplicated_path{$path}++;
-
- }
- for my $pool (@pools) {
- my @vols;
- for ( 1 .. 10) {
+ my @vols;
+ for ( 1 .. 10) {
+ next if !$pool->is_active;
eval { @vols = $pool->list_all_volumes() };
+ last if $@ && ref($@) && $@->code == 55;
last if !$@ || $@ =~ / no storage pool with matching uuid/;
warn "WARNING: on search volume_re: $@";
sleep 1;
- }
- push @volumes,@vols;
+ }
+ push @volumes,@vols;
}
return @volumes;
}
@@ -460,7 +454,7 @@ sub _find_all_volumes($self, $xml) {
return @used;
}
-sub _list_used_volumes($self) {
+sub list_used_volumes($self) {
my @used =$self->_list_used_volumes_known();
for my $name ( $self->discover ) {
my $dom = $self->vm->get_domain_by_name($name);
@@ -469,20 +463,14 @@ sub _list_used_volumes($self) {
return @used;
}
-sub list_unused_volumes($self) {
- my %used = map { $_ => 1 } $self->_list_used_volumes();
- my @unused;
+sub list_volumes($self) {
my $file;
+ my @volumes;
- my $n_found=0;
for my $vol ( $self->_list_volumes ) {
eval { ($file) = $vol->get_path };
confess $@ if $@ && $@ !~ /libvirt error code: 50,/;
- next if $used{$file};
-
- my $link = $self->_is_link($file);
- next if $link && $used{$link};
my $info;
eval { $info = $vol->get_info() };
@@ -491,11 +479,10 @@ sub list_unused_volumes($self) {
next if !$info || $info->{type} eq 2;
- # cluck Dumper([ $file, [sort grep /2023/,keys %used]]) if $file =~/2023/;
- push @unused,($file);
+ push @volumes,($file);
}
- return @unused;
+ return @volumes;
}
sub refresh_storage_pools($self) {
@@ -606,6 +593,7 @@ sub file_exists($self, $file) {
sub _file_exists_remote($self, $file) {
$file = $self->_follow_link($file) unless $file =~ /which$/;
for my $pool ($self->vm->list_all_storage_pools ) {
+ next if !$pool->is_active;
$self->_wait_storage( sub { $pool->refresh() } );
my @volumes = $self->_wait_storage( sub { $pool->list_all_volumes });
for my $vol ( @volumes ) {
@@ -717,6 +705,14 @@ sub create_storage_pool($self, $name, $dir, $vm=$self->vm) {
}
+sub remove_storage_pool($self, $name) {
+ my $sp = $self->vm->get_storage_pool_by_name($name);
+ return if !$sp;
+
+ $sp->destroy if $sp->is_active;
+ $sp->undefine();
+}
+
sub _create_default_pool($self, $vm=$self->vm) {
my $dir = "/var/lib/libvirt/images";
mkdir $dir if ! -e $dir;
diff --git a/lib/Ravada/VM/Void.pm b/lib/Ravada/VM/Void.pm
index 08f6b6c01..1cee3f0c7 100644
--- a/lib/Ravada/VM/Void.pm
+++ b/lib/Ravada/VM/Void.pm
@@ -352,7 +352,7 @@ sub search_volume($self, $pattern) {
return;
}
-sub _list_used_volumes($self) {
+sub list_used_volumes($self) {
my @disk;
for my $domain ($self->list_domains) {
push @disk,($domain->list_disks());
@@ -363,32 +363,33 @@ sub _list_used_volumes($self) {
return @disk
}
-sub _list_volumes($self) {
+sub _list_volumes_sp($self, $sp) {
die "Error: TODO remote!" if !$self->is_local;
+ confess if !defined $sp;
+ my $dir = $sp->{path} or die "Error: unknown path ".Dumper($sp);
+ return if ! -e $dir;
+
my @vol;
- opendir my $ls,$self->dir_img or die $!;
- my $dir = $self->dir_img;
+ opendir my $ls,$dir or die "$! $dir";
while (my $file = readdir $ls) {
- push @vol,("$dir/$file");
+ push @vol,("$dir/$file") if -f "$dir/$file";
}
closedir $ls;
+
return @vol;
}
-sub list_unused_volumes($self) {
- die "Error: TODO remote!" if !$self->is_local;
- my %used = map { $_ => 1 } $self->_list_used_volumes();
- my @unused;
- for my $vol ( sort $self->_list_volumes ) {
- next if ! -f $vol;
- next if $vol =~ m{/\..*yml$};
-
- push @unused,($vol) unless $used{$vol};
+sub list_volumes($self) {
+ my @volumes;
+ for my $sp ($self->list_storage_pools(1)) {
+ for my $vol ( $self->_list_volumes_sp($sp) ) {
+ push @volumes,($vol);
+ }
}
- return @unused;
+ return @volumes;
}
sub _search_volume_remote($self, $pattern) {
@@ -648,6 +649,19 @@ sub active_storage_pool($self, $name, $value) {
$self->write_file($file_sp, Dump( \@list));
}
+sub remove_storage_pool($self, $name) {
+ die "TODO remote VM" unless $self->is_local;
+
+ my $file_sp = $self->dir_img."/.storage_pools.yml";
+ my $sp_list = LoadFile($file_sp);
+ my @sp2;
+ for my $sp (@$sp_list) {
+ push @sp2,($sp) if $sp->{name} ne $name;
+ }
+
+ $self->write_file($file_sp, Dump( \@sp2));
+}
+
#########################################################################3
1;
diff --git a/t/lib/Test/Ravada.pm b/t/lib/Test/Ravada.pm
index d693d7232..0f62448f6 100644
--- a/t/lib/Test/Ravada.pm
+++ b/t/lib/Test/Ravada.pm
@@ -1023,7 +1023,12 @@ sub _activate_storage_pools($vm) {
next if $sp->is_active;
next unless $sp->get_name =~ /^tst_/;
diag("Activating sp ".$sp->get_name." on ".$vm->name);
- $sp->build() unless $sp->is_active;
+ my $xml = XML::LibXML->load_xml(string => $sp->get_xml_description());
+ my ($path) = $xml->findnodes('/pool/target/path');
+ my $dir = $path->textContent();
+ mkdir $dir or die "$! $dir" if ! -e $dir;
+
+ $sp->build();
$sp->create() unless $sp->is_active;
}
}
@@ -1426,7 +1431,7 @@ sub _qemu_storage_pool {
}
sub remove_qemu_pools($vm=undef) {
- return if !$VM_VALID{'KVM'} || $>;
+ return if !$vm && (!$VM_VALID{'KVM'} || $>);
return if defined $vm && $vm->type eq 'Void';
if (!defined $vm) {
eval { $vm = rvd_back->search_vm('KVM') };
@@ -1441,18 +1446,22 @@ sub remove_qemu_pools($vm=undef) {
my $base = base_pool_name();
$vm->connect();
- for my $pool ( Ravada::VM::KVM::_list_storage_pools($vm->vm)) {
+ for my $pool ( $vm->vm->list_all_storage_pools) {
my $name = $pool->get_name;
+ next if $name !~ qr/^$base/;
+ diag($name);
+
eval {$pool->build(Sys::Virt::StoragePool::BUILD_NEW); $pool->create() };
warn $@ if $@ && $@ !~ /already active/;
- next if $name !~ qr/^$base/;
- diag("Removing ".$vm->name." storage_pool ".$pool->get_name);
- for my $vol ( $pool->list_volumes ) {
- diag("Removing ".$pool->get_name." vol ".$vol->get_name);
- $vol->delete();
+ if ($pool->is_active) {
+ diag("Removing ".$vm->name." storage_pool ".$pool->get_name);
+ for my $vol ( $pool->list_volumes ) {
+ diag("Removing ".$pool->get_name." vol ".$vol->get_name);
+ $vol->delete();
+ }
}
_delete_qemu_pool($pool);
- $pool->destroy();
+ $pool->destroy() if $pool->is_active;
eval { $pool->undefine() };
warn $@ if $@;
warn $@ if$@ && $@ !~ /libvirt error code: 49,/;
diff --git a/t/storage_list_unused.t b/t/storage_list_unused.t
index 92750ce56..d76d23943 100644
--- a/t/storage_list_unused.t
+++ b/t/storage_list_unused.t
@@ -14,8 +14,6 @@ use Test::Ravada;
no warnings "experimental::signatures";
use feature qw(signatures);
-my @FILES;
-my @DIRS;
########################################################################
sub _new_file($vm) {
@@ -28,14 +26,14 @@ sub _new_file($vm) {
return $file;
}
-sub test_links($vm, $machine) {
+sub test_links($vm) {
+ my $machine = _create_clone($vm);
my $dir = $vm->dir_img();
my ($vol) = $machine->list_volumes();
my ($file) = $vol =~ m{.*/(.*)};
my $dst = "/var/tmp/$file";
unlink $dst or die "$! $dst" if -e $dst;
- push @FILES,($dst);
copy($vol,$dst) or die "$! $vol -> $dst";
unlink $vol or die "$! $vol";
@@ -60,6 +58,7 @@ sub test_links($vm, $machine) {
ok(!$found,"Expecting $vol not found") or die Dumper([$machine->list_volumes]);
+ remove_domain($machine);
}
sub test_links_dir($vm, $machine) {
@@ -68,19 +67,16 @@ sub test_links_dir($vm, $machine) {
my ($vol) = $machine->list_volumes();
my ($file) = $vol =~ m{.*/(.*)};
my $dir_dst = "/var/tmp/".new_domain_name();
+ remove_dir($dir_dst);
mkdir $dir_dst if ! -e $dir_dst;
- push @FILES,($dir_dst);
-
my $dir_link = "/var/tmp/".new_domain_name();
my $file_link = "$dir_link/$file";
- push @FILES,($file_link);
unlink($dir_link) or die "$! $dir_link"
if -e $dir_link;
symlink($dir_dst, $dir_link) or die "$! $dir_dst -> $dir_link";
- push @FILES,($dir_link);
my $dst = "$dir_dst/$file";
copy($vol,$dst) or die "$vol -> $dst";
@@ -119,6 +115,8 @@ sub test_links_dir($vm, $machine) {
ok(!$found,"Expecting $exp not found") or die Dumper([$machine->list_volumes]);
}
+ remove_dir($dir_dst);
+ remove_dir($dir_link);
}
@@ -132,11 +130,16 @@ sub test_list_unused_discover($vm, $machine) {
);
$sth->execute($machine->id);
+ my %used = map { $_ => 1 } $vm->list_used_volumes();
+ for my $vol (@volumes) {
+ ok($used{$vol}) or die Dumper([sort keys %used]);
+ }
+
my $req = Ravada::Request->list_unused_volumes(
uid => user_admin->id
,id_vm => $vm->id
,start => 0
- ,limit => 1000
+ ,limit => 0
);
wait_request();
my $out_json = $req->output;
@@ -198,18 +201,12 @@ sub test_list_unused($vm, $machine, $hidden_vols) {
my $dir = $vm->dir_img();
my $file = _new_file($vm);
- push @FILES,($file);
-
my $new_dir = $dir."/".new_domain_name();
- push @FILES,($new_dir);
if (! -e $new_dir) {
mkdir $new_dir or die "$! $new_dir";
}
- open my $out,">",$file or die "$! $file";
- print $out "hi\n";
- close $out;
$vm->refresh_storage();
my $req = Ravada::Request->list_unused_volumes(
@@ -236,6 +233,8 @@ sub test_list_unused($vm, $machine, $hidden_vols) {
ok(!$found_dir,"Expecting not found $new_dir");
_test_vm($vm, $machine, $output);
+ unlink $file;
+ remove_dir($new_dir);
}
sub test_page($vm) {
@@ -301,34 +300,6 @@ sub _used_volumes($machine) {
return @used;
}
-sub _clean_files($files=\@FILES) {
- my @dirs;
- for my $file (@$files) {
- if (-f $file || -l $file) {
- unlink $file or warn "$! $file";
- } elsif (-d $file) {
- push @dirs,($file);
- }
-
- }
-
- for my $file (@dirs) {
- my @files;
- my $pattern = base_domain_name();
- opendir my $ls,$file or die "$! $file";
- while (my $in = readdir $ls) {
- push @files,("$file/$in") if $in =~ /^$pattern/;
- }
- _clean_files(\@files);
- rmdir($file) or warn "$! $file";
- }
-
- for my $dir (@DIRS) {
- remove_dir($dir);
- }
-
-}
-
sub _create_clone($vm) {
my $base0 = create_domain($vm);
$base0->prepare_base(user_admin);
@@ -366,7 +337,9 @@ sub _create_clone_hide_bs($clone) {
return $clone2;
}
-sub test_remove($vm, $clone) {
+sub test_remove($vm) {
+ my $clone = _create_clone($vm);
+
my $file = _new_file($vm);
ok(-e $file) or exit;
my $user = create_user(new_domain_name(),"bar");
@@ -440,7 +413,51 @@ sub test_more($vm) {
ok(!$more);
}
+sub test_linked_sp_here($vm) {
+ diag("test_linked_sp_heare");
+ my $new_name1=new_domain_name();
+
+ my $new_dir = "/var/tmp/".$new_name1;
+ remove_dir($new_dir);
+ mkdir $new_dir or die "$! $new_dir";
+ $vm->create_storage_pool($new_name1, $new_dir)
+ if !grep { $_->{name} eq $new_name1} $vm->list_storage_pools(1);
+
+ my $new_name2 = new_domain_name();
+ my $new_link2 = "/var/tmp/".$new_name2;
+ remove_dir($new_link2);
+ symlink($new_dir,$new_link2) or die "$new_dir -> $new_link2";
+
+ $vm->create_storage_pool($new_name2, $new_link2)
+ if !grep { $_ eq $new_name2} $vm->list_storage_pools;
+
+ my $file = _touch_file($vm, $new_dir);
+
+ $vm->refresh_storage_pools();
+
+ my $req = Ravada::Request->list_unused_volumes(
+ uid => user_admin->id
+ ,id_vm => $vm->id
+ ,limit => 0
+ );
+ wait_request();
+ my $out_json = $req->output;
+ $out_json = '{}' if !defined $out_json;
+ my $output = decode_json($out_json);
+
+ my $list = $output->{list};
+ my @found = grep ($_->{file} =~ /^$new_dir/, @$list);
+ is( scalar(@found),1, "Found something ".Dumper(\@found));
+
+ remove_dir($new_dir);
+ remove_dir($new_link2);
+ $vm->remove_storage_pool($new_name1);
+ $vm->remove_storage_pool($new_name2);
+}
+
sub test_linked_sp($vm) {
+ diag("test linked sp");
+
my $dir = $vm->dir_img();
my $new_name=new_domain_name();
@@ -452,6 +469,8 @@ sub test_linked_sp($vm) {
$vm->create_storage_pool($new_name, $new_dir)
if !grep { $_ eq $new_name} $vm->list_storage_pools;
+ my $new_filename = _touch_file($vm, $dir);
+
if ($vm->type eq 'KVM') {
my $pool = $vm->vm->get_storage_pool_by_name($new_name);
$pool->create() if !$pool->is_active;
@@ -469,32 +488,43 @@ sub test_linked_sp($vm) {
my $output = decode_json($out_json);
my $list = $output->{list};
- my @found = grep ($_->{file} =~ /^$new_dir/, @$list);
- is( scalar(@found),0);
+ my @found = grep ($_->{file} =~ /$new_filename/, @$list);
+ is( scalar(@found),1) or die Dumper(\@found);
- if ($vm->type eq 'KVM') {
- my $pool = $vm->vm->get_storage_pool_by_name($new_name);
- $pool->destroy();
- $pool->undefine;
- }
+ $vm->remove_storage_pool($new_name);
unlink $new_dir;
+ unlink "$dir/$new_filename" or die $!;
}
sub remove_dir($dir) {
- return if !-e $dir;
- unlink $dir if -l $dir;
+ if ( -l $dir || -f $dir ) {
+ unlink $dir or die "$! $dir";
+ return;
+ }
my $base = base_domain_name;
if (-d $dir) {
opendir my $in,$dir or die "$! $dir";
while (my $file = readdir $in) {
next if $file =~ /^\.+$/;
- die "I will not delete $file" if $file !~ /^$base/;
+ die "I will not delete $dir/$file" if $file !~ /^$base/;
my $path = "$dir/$file";
remove_dir($path);
}
+ closedir $in;
rmdir $dir or die "$! $dir";
+ } else {
+ die "I don't know what is $dir" if -e $dir;
}
}
+sub _touch_file($vm, $dir) {
+ my $new_filename = new_domain_name();
+ my $new_file ="$dir/$new_filename";
+ open my $out,">",$new_file or die "$! $new_file";
+ close $out;
+ $vm->refresh_storage_pools();
+
+ return $new_filename;
+}
sub test_linked_sp_level2($vm) {
my $dir = $vm->dir_img();
@@ -502,8 +532,6 @@ sub test_linked_sp_level2($vm) {
my $new_dir = "/var/tmp/".$new_name;
- push @DIRS,($new_dir);
-
remove_dir($new_dir);
mkdir $new_dir or die "$! $new_dir";
@@ -513,6 +541,7 @@ sub test_linked_sp_level2($vm) {
remove_dir($new_link);
symlink($new_dir,$new_link) or die "$new_dir -> $new_link";
+ my $new_link0 = $new_link;
$new_link .="/".new_domain_name();
symlink($dir, $new_link) or die "$! $dir -> $new_link";
@@ -523,11 +552,7 @@ sub test_linked_sp_level2($vm) {
$vm->create_storage_pool($new_name, $new_link)
if !grep { $_ eq $new_name} $vm->list_storage_pools;
- if ($vm->type eq 'KVM') {
- my $pool = $vm->vm->get_storage_pool_by_name($new_name);
- $pool->create() if !$pool->is_active;
- $pool->refresh();
- }
+ my $new_filename = _touch_file($vm, $new_link);
my $req = Ravada::Request->list_unused_volumes(
uid => user_admin->id
@@ -540,19 +565,15 @@ sub test_linked_sp_level2($vm) {
my $output = decode_json($out_json);
my $list = $output->{list};
- my @found = grep ($_->{file} =~ /^$new_dir/, @$list);
- is( scalar(@found),0);
-
- @found = grep ($_->{file} =~ /^$new_link/, @$list);
- is( scalar(@found),0);
+ my @found = grep ($_->{file} =~ /$new_filename$/, @$list);
+ is( scalar(@found),1);
-
- if ($vm->type eq 'KVM') {
- my $pool = $vm->vm->get_storage_pool_by_name($new_name);
- $pool->destroy();
- $pool->undefine;
- }
- unlink $new_dir;
+ $vm->remove_storage_pool($new_name);
+ unlink "$dir/$new_filename" or die $!;
+ unlink "$new_link/$new_filename" or warn $!if -e "$new_link/$new_filename";
+ remove_dir($new_dir);
+ remove_dir($new_link0);
+ remove_dir($new_link);
}
sub test_linked_sp_level0($vm) {
@@ -565,14 +586,14 @@ sub test_linked_sp_level0($vm) {
symlink($dir,$new_link) or die "$dir -> $new_link";
- push @DIRS,($new_link);
-
my $followed = $vm->_follow_link($new_link);
is($followed,$dir) or die "Expecting followed link matches";
$vm->create_storage_pool($new_name, $new_link)
if !grep { $_ eq $new_name} $vm->list_storage_pools;
+ my $new_filename = _touch_file($vm, $dir);
+
if ($vm->type eq 'KVM') {
my $pool = $vm->vm->get_storage_pool_by_name($new_name);
$pool->create() if !$pool->is_active;
@@ -590,16 +611,49 @@ sub test_linked_sp_level0($vm) {
my $output = decode_json($out_json);
my $list = $output->{list};
- my @found = grep ($_->{file} =~ /^$new_link/, @$list);
- is( scalar(@found),0);
+ my @found = grep ($_->{file} =~ /$new_filename$/, @$list);
+ is( scalar(@found),1) or die "Expecting 1 $new_filename on $dir or $new_link";
+
+ $vm->remove_storage_pool($new_name);
+ unlink($new_link) or die $!;
+ unlink("$dir/$new_filename") or die $!;
+}
+sub _check_leftovers($vm, $delete=0) {
+ my $dir_run = "/run/user";
+ $dir_run .= "/$<" if $<;
+
+ my $base = base_domain_name();
+ for my $dir ($vm->dir_img, "/var/tmp",$dir_run) {
+ opendir my $ls,$dir or die "$! $dir";
+ while (my $file=readdir $ls) {
+ next if $file =~ /.(yml|void|lock|pid|qcow2)$/;
+ if ( $file =~ /$base/ ) {
+ if ($delete) {
+ remove_dir("$dir/$file");
+ } else {
+ confess "$dir/$file";
+ }
+ }
+ }
+ }
+ for my $sp ( $vm->list_storage_pools(1)) {
+ confess "SP found ".$sp->{name} if $sp->{name} =~ /$base/;
+ }
+}
+
+sub _clean_old_sps($vm) {
+ remove_qemu_pools($vm) if $vm->type eq 'KVM';
if ($vm->type eq 'KVM') {
- my $pool = $vm->vm->get_storage_pool_by_name($new_name);
- $pool->destroy();
- $pool->undefine;
+ my $base = base_domain_name();
+ for my $pool ( $vm->vm->list_all_storage_pools()) {
+ next if $pool->get_name !~ /^$base/;
+ $pool->destroy if $pool->is_active;
+ $pool->undefine;
+ }
}
- unlink $new_link;
}
+
########################################################################
init();
@@ -619,8 +673,23 @@ for my $vm_name ( vm_names() ) {
diag($msg) if !$vm;
skip $msg,10 if !$vm;
+ _clean_old_sps($vm);
+
+ _check_leftovers($vm,1);
+
my $clone = _create_clone($vm);
my @hidden_bs = _create_clone_hide_bs($clone);
+ _check_leftovers($vm);
+
+ test_linked_sp($vm);
+ _check_leftovers($vm);
+ test_linked_sp_here($vm);
+ _check_leftovers($vm);
+ test_linked_sp_level0($vm);
+ _check_leftovers($vm);
+
+ test_linked_sp_level2($vm);
+ _check_leftovers($vm);
test_linked_sp($vm);
test_linked_sp_level0($vm);
@@ -628,23 +697,28 @@ for my $vm_name ( vm_names() ) {
test_list_unused_discover($vm, $clone);
test_list_unused_discover2($vm);
+ _check_leftovers($vm);
test_list_unused($vm, $clone, \@hidden_bs);
-
+ _check_leftovers($vm);
test_links_dir($vm, $clone);
- test_links($vm, $clone);
+ _check_leftovers($vm);
+ test_links($vm);
+ _check_leftovers($vm);
test_page($vm);
- test_remove($vm, $clone);
+ test_remove($vm);
test_remove_many($vm);
test_more($vm);
+ remove_domain($clone);
+
+ _check_leftovers($vm);
}
}
-_clean_files();
end();
done_testing();
diff --git a/t/vm/s30_storage.t b/t/vm/s30_storage.t
index a13f2de23..27409f1ba 100644
--- a/t/vm/s30_storage.t
+++ b/t/vm/s30_storage.t
@@ -49,7 +49,7 @@ sub _add_fstab($vm) {
}
sub remove_fstab($vm, $dir) {
- my $file = "$dir/check_storage";# or die "$!";
+ my $file = "$dir/".base_domain_name()."check_storage";# or die "$!";
unlink $file or die "$! $file" if -e $file;
copy("/etc/fstab.tst_rvd_backup","/etc/fstab")
}
@@ -82,7 +82,7 @@ sub test_storage_pools_fail($vm) {
sub _clean_local {
my $dir = "$DIR/".base_domain_name();
- my $file = "$dir/check_storage";# or die "$!";
+ my $file = "$dir/".base_domain_name()."_check_storage";# or die "$!";
unlink $file or die "$! $file" if -e $file;
}
diff --git a/templates/main/list_bases_ng.html.ep b/templates/main/list_bases_ng.html.ep
index b4ab2656c..2571c49e2 100644
--- a/templates/main/list_bases_ng.html.ep
+++ b/templates/main/list_bases_ng.html.ep
@@ -35,8 +35,8 @@
ng-src="data:image/png;base64,{{machine.screenshot}}" alt="{{machine.alias}}" class="img-thumbnail" width="260"
>
+ ng-src="data:image/png;base64,{{machine.clone.screenshot}}" alt="{{machine.alias}}" class="img-thumbnail" width="260"
+ >
