From e4f7bcf661f0ea3bd5927c0cb94ffcfdde309af7 Mon Sep 17 00:00:00 2001 From: Digimer Date: Thu, 5 Apr 2018 02:25:56 -0400 Subject: [PATCH] * Created Storage->backup() that makes a backup of the given file under the Anvil! backup directory with a time-stamped suffix and preserving the original directory path. * Got anvil-configure-network writing out the new network config properly, but renaming already-active interfaces isn't working yet. * Updated System->get_ips() to record the interface name of a given network by MAC address using 'sys::mac::::iface'. Signed-off-by: Digimer --- Anvil/Tools.pm | 1 + Anvil/Tools/Database.pm | 2 +- Anvil/Tools/Storage.pm | 97 +++++++++++++++++++++ Anvil/Tools/System.pm | 19 ++++- share/words.xml | 6 ++ tools/anvil-configure-network | 154 ++++++++++++++++++++++++++++++---- 6 files changed, 258 insertions(+), 21 deletions(-) diff --git a/Anvil/Tools.pm b/Anvil/Tools.pm index ddbcda14..bcf58273 100755 --- a/Anvil/Tools.pm +++ b/Anvil/Tools.pm @@ -781,6 +781,7 @@ sub _set_paths psql => "/usr/bin/psql", 'postgresql-setup' => "/usr/bin/postgresql-setup", pwd => "/usr/bin/pwd", + rsync => "/usr/bin/rsync", su => "/usr/bin/su", systemctl => "/usr/bin/systemctl", touch => "/usr/bin/touch", diff --git a/Anvil/Tools/Database.pm b/Anvil/Tools/Database.pm index 9e2bbed1..881fafb6 100755 --- a/Anvil/Tools/Database.pm +++ b/Anvil/Tools/Database.pm @@ -1618,7 +1618,7 @@ sub insert_or_update_jobs my $self = shift; my $parameter = shift; my $anvil = $self->parent; - my $debug = defined $parameter->{debug} ? $parameter->{debug} : 2; + my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Database->insert_or_update_jobs()" }}); my $id = defined $parameter->{id} ? $parameter->{id} : ""; diff --git a/Anvil/Tools/Storage.pm b/Anvil/Tools/Storage.pm index c4bc7384..6c22ddee 100755 --- a/Anvil/Tools/Storage.pm +++ b/Anvil/Tools/Storage.pm @@ -12,6 +12,7 @@ our $VERSION = "3.0.0"; my $THIS_FILE = "Storage.pm"; ### Methods; +# backup_file # change_mode # change_owner # check_md5sums @@ -87,6 +88,102 @@ sub parent # Public methods # ############################################################################################################# +=head2 backup + +This will create a copy of the file under the C<< path::directories::backups >> directory with the datestamp as a suffix. The path is preserved under the backup directory. The path and file name are returned. + +By default, a failure to backup will be fatal with return code C<< 1 >> for safety reasons. If the file is critical, you can set C<< fatal => 0 >> and an empty string will be returned on error. + +Parameters; + +=head3 fatal (optional, default 1) + +If set to C<< 0 >>, any problem with the backup will be ignored and an empty string will be returned. + +=head3 file (required) + +This is the path and file name of the file to be backed up. Fully paths must be used. + +=cut +sub backup +{ + my $self = shift; + my $parameter = shift; + my $anvil = $self->parent; + my $debug = defined $parameter->{debug} ? $parameter->{debug} : 2; + + my $fatal = defined $parameter->{fatal} ? $parameter->{fatal} : 1; + my $source_file = defined $parameter->{file} ? $parameter->{file} : ""; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + source_file => $source_file, + fatal => $fatal, + }}); + + my $target_file = ""; + + if (not $source_file) + { + # No file passed in + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Storage->backup()", parameter => "target" }}); + if ($fatal) { $anvil->nice_exit({code => 1}); } + } + elsif ($source_file !~ /^\//) + { + # Isn't a full path + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0150", variables => { source_file => $source_file }}); + if ($fatal) { $anvil->nice_exit({code => 1}); } + } + elsif (not -e $source_file) + { + # File doesn't exist. + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0151", variables => { source_file => $source_file }}); + if ($fatal) { $anvil->nice_exit({code => 1}); } + } + elsif (not -f $source_file) + { + # Not a file + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0153", variables => { source_file => $source_file }}); + if ($fatal) { $anvil->nice_exit({code => 1}); } + } + elsif (not -r $source_file) + { + # Can't read the file. + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0152", variables => { source_file => $source_file }}); + if ($fatal) { $anvil->nice_exit({code => 1}); } + } + else + { + # Proceed with the backup. We'll recreate the path + my ($directory, $file) = ($source_file =~ /^(\/.*)\/(.*)$/); + my $timestamp = $anvil->Get->date_and_time({file_name => 1}); + my $backup_directory = $anvil->data->{path}{directories}{backups}.$directory; + my $backup_target = $file.".".$timestamp; + $target_file = $backup_directory."/".$backup_target; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + directory => $directory, + file => $file, + timestamp => $timestamp, + backup_directory => $backup_directory, + backup_target => $backup_target, + target_file => $target_file, + }}); + + # Backup! It will create the target directory, if needed. + $anvil->Storage->copy_file({ + source => $source_file, + target => $target_file, + debug => 2, + }); + + # Log that the file was backed up. + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0154", variables => { source_file => $source_file, target_file => $target_file }}); + } + + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { target_file => $target_file }}); + return($target_file); +} +=cut + =head2 change_mode This changes the mode of a file or directory. diff --git a/Anvil/Tools/System.pm b/Anvil/Tools/System.pm index 661cb46c..5b95da9d 100755 --- a/Anvil/Tools/System.pm +++ b/Anvil/Tools/System.pm @@ -368,7 +368,13 @@ sub enable_daemon =head2 get_ips -This method checks the local system for interfaces with IP addresses and stores them in C<< sys::network::interface::::ip >> and C<< sys::network::interface::::subnet >> +This method checks the local system for interfaces and stores them in: + +* C<< sys::network::interface::::ip >> - If an IP address is set +* C<< sys::network::interface::::subnet >> - If an IP is set +* C<< sys::network::interface::::mac >> - Always set. + +To aid in look-up by MAC address, C<< sys::mac::::iface >> is also set. =cut sub get_ips @@ -376,7 +382,7 @@ sub get_ips my $self = shift; my $parameter = shift; my $anvil = $self->parent; - my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; + my $debug = defined $parameter->{debug} ? $parameter->{debug} : 2; $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "System->get_ips()" }}); my $in_iface = ""; @@ -418,8 +424,13 @@ sub get_ips } if ($line =~ /ether ([0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}) /i) { - $anvil->data->{sys}{networks}{$in_iface}{mac} = $1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "sys::networks::${in_iface}::mac" => $anvil->data->{sys}{networks}{$in_iface}{mac} }}); + my $mac = $1; + $anvil->data->{sys}{networks}{$in_iface}{mac} = $mac; + $anvil->data->{sys}{mac}{$mac}{iface} = $in_iface; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "sys::networks::${in_iface}::mac" => $anvil->data->{sys}{networks}{$in_iface}{mac}, + "sys::mac::${mac}::iface" => $anvil->data->{sys}{mac}{$mac}{iface}, + }}); } } diff --git a/share/words.xml b/share/words.xml index 30bf54ea..d4f83cb1 100644 --- a/share/words.xml +++ b/share/words.xml @@ -223,6 +223,12 @@ The database connection error was: A job to configure the network was found, and it was picked up by: [#!variable!pid!#], but that process is not running and it appears to only be: [#!variable!percent!# %] complete. Taking the job. The network: [#!variable!network!#] has something set for the IP [#!variable!ip!#], but it appears to be invalid. Ignoring this network. The network: [#!variable!network!#] is not set to be configured. Skipping it. + The Storage->backup() method was called with the source file: [#!variable!source_file!#], which does not appear to be a full path and file name (should start with '/'). + The Storage->backup() method was called with the source file: [#!variable!source_file!#], which does not appear to exist. + The Storage->backup() method was called with the source file: [#!variable!source_file!#], which can not be read (please check permissions and SELinux). + The Storage->backup() method was called with the source file: [#!variable!source_file!#], which isn't actually a file. + The file: [#!variable!source_file!#] has been backed up as: [#!variable!target_file!#]. + Removing the old network configuration file: [#!variable!file!#] as part of the network reconfiguration. Test diff --git a/tools/anvil-configure-network b/tools/anvil-configure-network index 9a90de8d..d45f6c36 100755 --- a/tools/anvil-configure-network +++ b/tools/anvil-configure-network @@ -120,13 +120,16 @@ sub reconfigure_network $anvil->nice_exit({code => 2}); } + # Get the current list of IPs and MAC addresses. + $anvil->System->get_ips(); + # Now configure the network. my $dns = defined $anvil->data->{variables}{form}{config_step2}{dns}{value} ? [split/,/, $anvil->data->{variables}{form}{config_step2}{dns}{value}] : []; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { dns => $dns }}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { dns => $dns }}); for (my $i = 0; $i < @{$dns}; $i++) { $dns->[$i] = $anvil->Words->clean_spaces({ string => $dns->[$i] }); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { "dns->[$i]" => $dns->[$i] }}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { "dns->[$i]" => $dns->[$i] }}); } my $gateway = defined $anvil->data->{variables}{form}{config_step2}{gateway}{value} ? $anvil->data->{variables}{form}{config_step2}{gateway}{value} : ""; @@ -215,21 +218,47 @@ sub reconfigure_network $say_interface = "ifn".$network_count; $interface_prefix = "IFN"; } - my $bond_file = $anvil->data->{path}{directories}{ifcfg}."/ifcfg-".$interface_prefix."_".$network_count."_-_Bond_1"; - my $link2_file = $anvil->data->{path}{directories}{ifcfg}."/ifcfg-".$interface_prefix."_".$network_count."_-_Link_2"; - my $link1_file = $anvil->data->{path}{directories}{ifcfg}."/ifcfg-".$interface_prefix."_".$network_count."_-_Link_1"; - my $bond_uuid = get_uuid_from_interface_file($anvil, $bond_file); - my $link2_uuid = get_uuid_from_interface_file($anvil, $link2_file); - my $link1_uuid = get_uuid_from_interface_file($anvil, $link1_file); - my $say_defroute = $is_gateway ? "yes" : "no"; - my $cidr = $anvil->Convert->cidr({subnet => $subnet}); + my $say_defroute = $is_gateway ? "yes" : "no"; + my $cidr = $anvil->Convert->cidr({subnet => $subnet}); + my $bond_file = $anvil->data->{path}{directories}{ifcfg}."/ifcfg-".$interface_prefix."_".$network_count."_-_Bond_1"; + my $new_link1_file = $anvil->data->{path}{directories}{ifcfg}."/ifcfg-".$interface_prefix."_".$network_count."_-_Link_1"; + my $new_link2_file = $anvil->data->{path}{directories}{ifcfg}."/ifcfg-".$interface_prefix."_".$network_count."_-_Link_2"; + my $old_link1_file = $new_link1_file; + my $old_link2_file = $new_link2_file; + if ((exists $anvil->data->{sys}{mac}{$link1_mac}{iface}) && ($anvil->data->{sys}{mac}{$link1_mac}{iface})) + { + $old_link1_file = $anvil->data->{path}{directories}{ifcfg}."/ifcfg-".$anvil->data->{sys}{mac}{$link1_mac}{iface}; + } + if ((exists $anvil->data->{sys}{mac}{$link2_mac}{iface}) && ($anvil->data->{sys}{mac}{$link2_mac}{iface})) + { + $old_link2_file = $anvil->data->{path}{directories}{ifcfg}."/ifcfg-".$anvil->data->{sys}{mac}{$link2_mac}{iface}; + } + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + say_defroute => $say_defroute, + cidr => $cidr, + bond_file => $bond_file, + new_link2_file => $new_link2_file, + new_link1_file => $new_link1_file, + old_link1_file => $old_link1_file, + old_link2_file => $old_link2_file, + }}); + + # Gather (or create) UUIDs + my $bond_uuid = get_uuid_from_interface_file($anvil, $bond_file); + my $link1_uuid = get_uuid_from_interface_file($anvil, $old_link1_file); + my $link2_uuid = get_uuid_from_interface_file($anvil, $old_link2_file); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + bond_uuid => $bond_uuid, + link1_uuid => $link1_uuid, + link2_uuid => $link2_uuid, + }}); ### TODO: Set the firewall Zone appropriately. # Build the Bond config. my $bond_config = "# $say_network - Bond 1\n"; - $bond_config .= "DEVICE=\"".$say_interface."_bond1\"\n"; - $bond_config .= "NAME=\"".$interface_prefix." ".$network_count." - Bond 1\"\n"; $bond_config .= "UUID=\"".$bond_uuid."\"\n"; + $bond_config .= "NAME=\"".$interface_prefix." ".$network_count." - Bond 1\"\n"; + $bond_config .= "DEVICE=\"".$say_interface."_bond1\"\n"; $bond_config .= "BONDING_OPTS=\"mode=active-backup primary=".$say_interface."_link1 updelay=120000 downdelay=0 miimon=100 primary_reselect=better\"\n"; $bond_config .= "TYPE=\"Bond\"\n"; $bond_config .= "BONDING_MASTER=\"yes\"\n"; @@ -248,12 +277,103 @@ sub reconfigure_network } $bond_config .= "DEFROUTE=\"".$say_defroute."\"\n"; $bond_config .= "ZONE=\"".$say_interface."\""; + + my $link1_config = "# $say_network - Link 1\n"; + $link1_config .= "HWADDR=\"".uc($link1_mac)."\"\n"; + $link1_config .= "UUID=\"".$link1_uuid."\"\n"; + $link1_config .= "NAME=\"".$interface_prefix." ".$network_count." - Link 1\"\n"; + $link1_config .= "DEVICE=\"".$say_interface."_link1\"\n"; + $link1_config .= "TYPE=\"Ethernet\"\n"; + $link1_config .= "BOOTPROTO=\"none\"\n"; + $link1_config .= "IPV6INIT=\"no\"\n"; + $link1_config .= "ONBOOT=\"yes\"\n"; + $link1_config .= "USERCTL=\"no\"\n"; + $link1_config .= "MTU=\"1500\"\n"; # TODO: Make the MTU user-adjustable + $link1_config .= "NM_CONTROLLED=\"yes\"\n"; + $link1_config .= "SLAVE=\"yes\"\n"; + $link1_config .= "MASTER=\"".$say_interface."_bond1\"\n"; + $link1_config .= "ZONE=\"".$say_interface.""; + + my $link2_config = "# $say_network - Link 2\n"; + $link2_config .= "HWADDR=\"".uc($link2_mac)."\"\n"; + $link2_config .= "UUID=\"".$link2_uuid."\"\n"; + $link2_config .= "NAME=\"".$interface_prefix." ".$network_count." - Link 2\"\n"; + $link2_config .= "DEVICE=\"".$say_interface."_link2\"\n"; + $link2_config .= "TYPE=\"Ethernet\"\n"; + $link2_config .= "BOOTPROTO=\"none\"\n"; + $link2_config .= "IPV6INIT=\"no\"\n"; + $link2_config .= "ONBOOT=\"yes\"\n"; + $link2_config .= "USERCTL=\"no\"\n"; + $link2_config .= "MTU=\"1500\"\n"; # TODO: Make the MTU user-adjustable + $link2_config .= "NM_CONTROLLED=\"yes\"\n"; + $link2_config .= "SLAVE=\"yes\"\n"; + $link2_config .= "MASTER=\"".$say_interface."_bond1\"\n"; + $link2_config .= "ZONE=\"".$say_interface.""; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { - bond_config => $bond_config, + bond_config => $bond_config, + link1_config => $link1_config, + link2_config => $link2_config, }}); - - my $link1_config = ""; - my $link2_config = ""; + + # Make backups of existing files + if (-e $bond_file) { $anvil->Storage->backup({file => $bond_file}); } + if (-e $old_link1_file) { $anvil->Storage->backup({file => $old_link1_file}); } + if (-e $old_link2_file) { $anvil->Storage->backup({file => $old_link1_file}); } + if (-e $new_link1_file) { $anvil->Storage->backup({file => $new_link1_file}); } + if (-e $new_link2_file) { $anvil->Storage->backup({file => $new_link1_file}); } + + ### Write out the new configs + # Bond + $anvil->Storage->write_file({ + file => $bond_file, + body => $bond_config, + user => "root", + group => "root", + mode => 0644, + }); + # Link 1 + $anvil->Storage->write_file({ + file => $new_link1_file, + body => $link1_config, + user => "root", + group => "root", + mode => 0644, + }); + # Link 2 + $anvil->Storage->write_file({ + file => $new_link2_file, + body => $link2_config, + user => "root", + group => "root", + mode => 0644, + }); + + # Drop the old interfaces and remove their configs if the interface name has changed. + if ($old_link1_file ne $new_link1_file) + { + # Doesn't match, unlink the old file. + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, key => "log_0155", variables => { file => $old_link1_file }}); + unlink $old_link1_file; + + # Stop the old network interface and rename it. + my $old_iface = $anvil->data->{sys}{mac}{$link1_mac}{iface}; + my $new_iface = $say_interface."_link1"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + old_iface => $old_iface, + new_iface => $new_iface, + }}); + + ### TODO: This isn't working for already-active interfaces... Says the new interface can't be found. + # Drop the old interface + $anvil->System->call({shell_call => $anvil->data->{path}{exe}{ip}." link set ".$old_iface." down"}); + + # Rename it + $anvil->System->call({shell_call => $anvil->data->{path}{exe}{ip}." link set ".$old_iface." name ".$new_iface}); + + # Bring up the interface with the new name + $anvil->System->call({shell_call => $anvil->data->{path}{exe}{ip}." link set ".$old_iface." up"}); + } } elsif ((exists $anvil->data->{variables}{form}{config_step2}{$link1_key}{value}) && ($anvil->Validate->is_mac({mac => $anvil->data->{variables}{form}{config_step2}{$link1_key}{value}}))) { @@ -269,6 +389,8 @@ sub reconfigure_network } } } + + # Reload the network return(0); }