From 7710d9d10989bfc1a7917f4f346f94390b03c07c Mon Sep 17 00:00:00 2001 From: digimer Date: Fri, 3 Feb 2023 22:05:34 -0500 Subject: [PATCH] * Created the new anvil-manage-server-storage tool which will specifically handle managing a server's disks. * Created DRBD->parse_resource() to pass a specific DRBD resource's XML data. * Fixed a bug in Get->available_resources() so that if the threads is lower than CPU cores, the cores are used as the total available to VMs. * Fixed bugs in Get->server_from_switch() where it just wasn't working properly. * Updated scan_drbd to not reset a resource's size to 0-bytes when a resource goes offline. Signed-off-by: digimer --- Anvil/Tools/DRBD.pm | 248 +++++++++++++++++ Anvil/Tools/Get.pm | 61 ++++- Anvil/Tools/Server.pm | 18 +- man/Makefile.am | 1 + man/anvil-manage-server-storage.8 | 0 scancore-agents/scan-drbd/scan-drbd | 10 + tools/Makefile.am | 2 +- tools/anvil-daemon | 2 +- tools/anvil-manage-server-storage | 398 ++++++++++++++++++++++++++++ 9 files changed, 724 insertions(+), 16 deletions(-) create mode 100644 man/anvil-manage-server-storage.8 create mode 100755 tools/anvil-manage-server-storage diff --git a/Anvil/Tools/DRBD.pm b/Anvil/Tools/DRBD.pm index 76dc739f..cb175545 100644 --- a/Anvil/Tools/DRBD.pm +++ b/Anvil/Tools/DRBD.pm @@ -24,6 +24,7 @@ my $THIS_FILE = "DRBD.pm"; # get_next_resource # get_status # manage_resource +# parse_resource # reload_defaults # remove_backing_lv # resource_uuid @@ -2361,6 +2362,253 @@ sub manage_resource return($return_code); } + +=head2 parse_resource + +This takes the XML from a specific DRBD resource and parses it. + +Parameters; + +=head3 xml (required) + +This is the XML to parse, generally as stored in the C<< scan_drbd_resources >> -> C<< scan_drbd_resource_xml >>. + +=cut +sub parse_resource +{ + my $self = shift; + my $parameter = shift; + my $anvil = $self->parent; + my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "DRBD->parse_resource()" }}); + + my $xml = defined $parameter->{xml} ? $parameter->{xml} : ""; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + xml => $xml, + }}); + + if (not $xml) + { + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "DRBD->parse_resource()", parameter => "xml" }}); + return("!!error!!"); + } + + local $@; + my $dom = eval { XML::LibXML->load_xml(string => $xml); }; + if ($@) + { + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "error_0253", variables => { + xml => $xml, + error => $@, + }}); + return(1); + } + else + { + # Successful parse! +=cut + + + + /dev/drbd_srv01-fs37_0 + /dev/cs_vm-a01n01/srv01-fs37_0 + internal + + + /dev/drbd_srv01-fs37_1 + /dev/cs_vm-a01n01/srv01-fs37_1 + internal + +
(null)
+
+ + + /dev/drbd_srv01-fs37_0 + /dev/cs_vm-a01n02/srv01-fs37_0 + internal + + + /dev/drbd_srv01-fs37_1 + /dev/cs_vm-a01n02/srv01-fs37_1 + internal + +
(null)
+
+ + + /dev/drbd_srv01-fs37_0 + /dev/cs_vm-a01dr01/srv01-fs37_0 + internal + + + /dev/drbd_srv01-fs37_1 + /dev/cs_vm-a01dr01/srv01-fs37_1 + internal + +
(null)
+
+ +
10.101.10.1
+
10.101.10.2
+
+
+
+
+
+ +
10.201.10.1
+
10.201.10.3
+
+
+
+
+
+ +
10.201.10.2
+
10.201.10.3
+
+
+
+
+
+
+=cut + foreach my $name ($dom->findnodes('/resource')) + { + my $resource = $name->{name}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { resource => $resource }}); + + foreach my $host ($name->findnodes('./host')) + { + my $this_host_name = $host->{name}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { this_host_name => $this_host_name }}); + + # Record the details under the hosts + foreach my $volume_vnr ($host->findnodes('./volume')) + { + my $volume = $volume_vnr->{vnr}; + my $meta_disk = $volume_vnr->findvalue('./meta-disk'); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + 's1:volume' => $volume, + 's2:meta_disk' => $meta_disk, + }}); + + my $host_uuid = $anvil->Get->host_uuid_from_name({host_name => $this_host_name}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { host_uuid => $host_uuid }}); + + $anvil->data->{new}{resource}{$resource}{host_name}{$this_host_name}{host_uuid} = $host_uuid; + $anvil->data->{new}{resource}{$resource}{host_uuid}{$host_uuid}{volume_number}{$volume}{device_path} = $volume_vnr->findvalue('./device'); + $anvil->data->{new}{resource}{$resource}{host_uuid}{$host_uuid}{volume_number}{$volume}{backing_disk} = $volume_vnr->findvalue('./disk'); + $anvil->data->{new}{resource}{$resource}{host_uuid}{$host_uuid}{volume_number}{$volume}{device_minor} = $volume_vnr->findvalue('./device/@minor'); + $anvil->data->{new}{resource}{$resource}{host_uuid}{$host_uuid}{volume_number}{$volume}{meta_disk} = $meta_disk; + $anvil->data->{new}{resource}{$resource}{host_uuid}{$host_uuid}{volume_number}{$volume}{size} = 0; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "s1:new::resource::${resource}::host_name::${this_host_name}::host_uuid" => $anvil->data->{new}{resource}{$resource}{host_name}{$this_host_name}{host_uuid}, + "s2:new::resource::${resource}::host_uuid::${host_uuid}::volume_number::${volume}::device_path" => $anvil->data->{new}{resource}{$resource}{host_uuid}{$host_uuid}{volume_number}{$volume}{device_path}, + "s3:new::resource::${resource}::host_uuid::${host_uuid}::volume_number::${volume}::backing_disk" => $anvil->data->{new}{resource}{$resource}{host_uuid}{$host_uuid}{volume_number}{$volume}{backing_disk}, + "s4:new::resource::${resource}::host_uuid::${host_uuid}::volume_number::${volume}::device_minor" => $anvil->data->{new}{resource}{$resource}{host_uuid}{$host_uuid}{volume_number}{$volume}{device_minor}, + "s5:new::resource::${resource}::host_uuid::${host_uuid}::volume_number::${volume}::meta_disk" => $anvil->data->{new}{resource}{$resource}{host_uuid}{$host_uuid}{volume_number}{$volume}{meta_disk}, + }}); + } + } + foreach my $connection ($name->findnodes('./connection')) + { + my $host1_name = ""; + my $host1_ip_address = ""; + my $host1_tcp_port = ""; + my $host2_name = ""; + my $host2_ip_address = ""; + my $host2_tcp_port = ""; + my $peer = ""; + foreach my $host ($connection->findnodes('./host')) + { + my $this_host_name = $host->{name}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { this_host_name => $this_host_name }}); + if (not $host1_name) + { + $host1_name = $this_host_name; + $host1_ip_address = $host->findvalue('./address'); + $host1_tcp_port = $host->findvalue('./address/@port'); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + host1_name => $host1_name, + host1_ip_address => $host1_ip_address, + host1_tcp_port => $host1_tcp_port, + }}); + } + else + { + $host2_name = $this_host_name; + $host2_ip_address = $host->findvalue('./address'); + $host2_tcp_port = $host->findvalue('./address/@port'); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + host2_name => $host2_name, + host2_ip_address => $host2_ip_address, + host2_tcp_port => $host2_tcp_port, + }}); + + $anvil->data->{new}{resource}{$resource}{host1_to_host2}{$host1_name}{$host2_name}{host1_ip_address} = $host1_ip_address; + $anvil->data->{new}{resource}{$resource}{host1_to_host2}{$host1_name}{$host2_name}{host1_tcp_port} = $host1_tcp_port; + $anvil->data->{new}{resource}{$resource}{host1_to_host2}{$host1_name}{$host2_name}{host2_ip_address} = $host2_ip_address; + $anvil->data->{new}{resource}{$resource}{host1_to_host2}{$host1_name}{$host2_name}{host2_tcp_port} = $host2_tcp_port; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "s1:new::resource::${resource}::host1_to_host2::${host1_name}::${host2_name}::host1_ip_address" => $anvil->data->{new}{resource}{$resource}{host1_to_host2}{$host1_name}{$host2_name}{host1_ip_address}, + "s2:new::resource::${resource}::host1_to_host2::${host1_name}::${host2_name}::host1_tcp_port" => $anvil->data->{new}{resource}{$resource}{host1_to_host2}{$host1_name}{$host2_name}{host1_tcp_port}, + "s3:new::resource::${resource}::host1_to_host2::${host1_name}::${host2_name}::host2_ip_address" => $anvil->data->{new}{resource}{$resource}{host1_to_host2}{$host1_name}{$host2_name}{host2_ip_address}, + "s4:new::resource::${resource}::host1_to_host2::${host1_name}::${host2_name}::host2_tcp_port" => $anvil->data->{new}{resource}{$resource}{host1_to_host2}{$host1_name}{$host2_name}{host2_tcp_port}, + }}); + + foreach my $proxy ($host->findnodes('./proxy')) + { + my $host_name = $proxy->{hostname}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { host_name => $host_name }}); + + # This should always be the target, but lets be safe/careful + next if $host_name ne $host2_name; + + $anvil->data->{new}{resource}{$resource}{host1_to_host2}{$host1_name}{$host2_name}{host2_inside_ip_address} = $proxy->findvalue('./inside'); + $anvil->data->{new}{resource}{$resource}{host1_to_host2}{$host1_name}{$host2_name}{host2_inside_tcp_port} = $proxy->findvalue('./inside/@port'); + $anvil->data->{new}{resource}{$resource}{host1_to_host2}{$host1_name}{$host2_name}{host2_outside_ip_address} = $proxy->findvalue('./outside'); + $anvil->data->{new}{resource}{$resource}{host1_to_host2}{$host1_name}{$host2_name}{host2_outside_tcp_port} = $proxy->findvalue('./outside/@port'); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "s1:new::resource::${resource}::host1_to_host2::${host1_name}::${host2_name}::host2_inside_ip_address" => $anvil->data->{new}{resource}{$resource}{host1_to_host2}{$host1_name}{$host2_name}{host2_inside_ip_address}, + "s2:new::resource::${resource}::host1_to_host2::${host1_name}::${host2_name}::host2_inside_tcp_port" => $anvil->data->{new}{resource}{$resource}{host1_to_host2}{$host1_name}{$host2_name}{host2_inside_tcp_port}, + "s3:new::resource::${resource}::host1_to_host2::${host1_name}::${host2_name}::host2_outside_ip_address" => $anvil->data->{new}{resource}{$resource}{host1_to_host2}{$host1_name}{$host2_name}{host2_outside_ip_address}, + "s4:new::resource::${resource}::host1_to_host2::${host1_name}::${host2_name}::host2_outside_tcp_port" => $anvil->data->{new}{resource}{$resource}{host1_to_host2}{$host1_name}{$host2_name}{host2_outside_tcp_port}, + }}); + + $anvil->data->{new}{resource}{$resource}{proxy}{$host_name}{inside}{ip_address} = $proxy->findvalue('./inside'); + $anvil->data->{new}{resource}{$resource}{proxy}{$host_name}{inside}{tcp_port} = $proxy->findvalue('./inside/@port'); + $anvil->data->{new}{resource}{$resource}{proxy}{$host_name}{outside}{ip_address} = $proxy->findvalue('./outside'); + $anvil->data->{new}{resource}{$resource}{proxy}{$host_name}{outside}{tcp_port} = $proxy->findvalue('./outside/@port'); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "new::resource::${resource}::proxy::${host_name}::inside::ip_address" => $anvil->data->{new}{resource}{$resource}{proxy}{$host_name}{inside}{ip_address}, + "new::resource::${resource}::proxy::${host_name}::inside::tcp_port" => $anvil->data->{new}{resource}{$resource}{proxy}{$host_name}{inside}{tcp_port}, + "new::resource::${resource}::proxy::${host_name}::outside::ip_address" => $anvil->data->{new}{resource}{$resource}{proxy}{$host_name}{outside}{ip_address}, + "new::resource::${resource}::proxy::${host_name}::outside::tcp_port" => $anvil->data->{new}{resource}{$resource}{proxy}{$host_name}{outside}{tcp_port}, + }}); + } + } + } + } + } + } + + return(0); +} + + =head2 reload_defaults This switches DRBD back to running using the values in the config files. Specifically, it calls C<< drbdadm adjust all >>. diff --git a/Anvil/Tools/Get.pm b/Anvil/Tools/Get.pm index 4df0a8fe..2fddbbda 100644 --- a/Anvil/Tools/Get.pm +++ b/Anvil/Tools/Get.pm @@ -643,7 +643,7 @@ WHERE # How many cores? if ((not $anvil->data->{anvil_resources}{$anvil_uuid}{cpu}{cores}) or - ($scan_hardware_cpu_cores < $anvil->data->{anvil_resources}{$anvil_uuid}{cpu}{cores})) + ($scan_hardware_cpu_cores < $anvil->data->{anvil_resources}{$anvil_uuid}{cpu}{cores})) { $anvil->data->{anvil_resources}{$anvil_uuid}{cpu}{cores} = $scan_hardware_cpu_cores; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { @@ -651,15 +651,25 @@ WHERE }}); } if ((not $anvil->data->{anvil_resources}{$anvil_uuid}{cpu}{threads}) or - ($scan_hardware_cpu_threads < $anvil->data->{anvil_resources}{$anvil_uuid}{cpu}{threads})) + ($scan_hardware_cpu_threads < $anvil->data->{anvil_resources}{$anvil_uuid}{cpu}{threads})) { $anvil->data->{anvil_resources}{$anvil_uuid}{cpu}{threads} = $scan_hardware_cpu_threads; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "anvil_resources::${anvil_uuid}::cpu::threads" => $anvil->data->{anvil_resources}{$anvil_uuid}{cpu}{threads}, }}); } + + # If there are less threads than cores, set the cores to be equal to threads. + if ($anvil->data->{anvil_resources}{$anvil_uuid}{cpu}{threads} < $anvil->data->{anvil_resources}{$anvil_uuid}{cpu}{cores}) + { + $anvil->data->{anvil_resources}{$anvil_uuid}{cpu}{threads} = $anvil->data->{anvil_resources}{$anvil_uuid}{cpu}{cores}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "anvil_resources::${anvil_uuid}::cpu::threads" => $anvil->data->{anvil_resources}{$anvil_uuid}{cpu}{threads}, + }}); + } + if ((not $anvil->data->{anvil_resources}{$anvil_uuid}{ram}{available}) or - ($scan_hardware_ram_total < $anvil->data->{anvil_resources}{$anvil_uuid}{ram}{hardware})) + ($scan_hardware_ram_total < $anvil->data->{anvil_resources}{$anvil_uuid}{ram}{hardware})) { $anvil->data->{anvil_resources}{$anvil_uuid}{ram}{available} = $scan_hardware_ram_total; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { @@ -2114,9 +2124,9 @@ sub server_from_switch server_string => $server_string, }}); - if ((not $server_string) && ($anvil->data->{switches}{'anvil'})) + if ((not $server_string) && ($anvil->data->{switches}{'server'})) { - $server_string = $anvil->data->{switches}{'anvil'}; + $server_string = $anvil->data->{switches}{'server'}; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { server_string => $server_string }}); } if (not $server_string) @@ -2125,10 +2135,16 @@ sub server_from_switch return("!!error!!", ""); } + $anvil->Database->get_anvils({debug => $debug}); $anvil->Database->get_servers({debug => $debug}); $anvil->data->{switches}{server_name} = "" if not exists $anvil->data->{switches}{server_name}; $anvil->data->{switches}{server_uuid} = "" if not exists $anvil->data->{switches}{server_uuid}; - if (exists $anvil->data->{servers}{server_uuid}{$server_string}{server_name}) + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + server_string => $server_string, + "switches::server_name" => $anvil->data->{switches}{server_name}, + "switches::server_uuid" => $anvil->data->{switches}{server_uuid}, + }}); + if (exists $anvil->data->{servers}{server_uuid}{$server_string}) { # Found it by UUID. $anvil->data->{switches}{server_name} = $anvil->data->{anvils}{server_uuid}{$server_string}{server_name}; @@ -2138,14 +2154,33 @@ sub server_from_switch "switches::server_uuid" => $anvil->data->{switches}{server_uuid}, }}); } - elsif (exists $anvil->data->{anvils}{server_uuid}{$server_string}) + else { - $anvil->data->{switches}{server_name} = $server_string; - $anvil->data->{switches}{server_uuid} = $anvil->data->{anvils}{server_uuid}{$server_string}{server_uuid}; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - "switches::server_name" => $anvil->data->{switches}{server_name}, - "switches::server_uuid" => $anvil->data->{switches}{server_uuid}, - }}); + # If we have an anvil_uuid, see if the server exists there. + foreach my $this_anvil_name (sort {$a cmp $b} keys %{$anvil->data->{anvils}{anvil_name}}) + { + my $this_anvil_uuid = $anvil->data->{anvils}{anvil_name}{$this_anvil_name}{anvil_uuid}; + if (($anvil_uuid) && ($anvil_uuid ne $this_anvil_uuid)) + { + next; + } + foreach my $this_server_name (sort {$a cmp $b} keys %{$anvil->data->{servers}{anvil_uuid}{$this_anvil_uuid}{server_name}}) + { + my $this_server_uuid = $anvil->data->{servers}{anvil_uuid}{$this_anvil_uuid}{server_name}{$this_server_name}{server_uuid}; + if (($server_string eq $this_server_name) or + ($server_string eq $this_server_uuid)) + { + # Found it + $anvil->data->{switches}{server_name} = $this_server_name; + $anvil->data->{switches}{server_uuid} = $this_server_uuid; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "switches::server_name" => $anvil->data->{switches}{server_name}, + "switches::server_uuid" => $anvil->data->{switches}{server_uuid}, + }}); + last; + } + } + } } return($anvil->data->{switches}{server_name}, $anvil->data->{switches}{server_uuid}); diff --git a/Anvil/Tools/Server.pm b/Anvil/Tools/Server.pm index d3b169a7..7efc0176 100644 --- a/Anvil/Tools/Server.pm +++ b/Anvil/Tools/Server.pm @@ -1385,7 +1385,7 @@ sub parse_definition my $server = defined $parameter->{server} ? $parameter->{server} : ""; my $source = defined $parameter->{source} ? $parameter->{source} : ""; my $definition = defined $parameter->{definition} ? $parameter->{definition} : ""; - my $host = defined $parameter->{host} ? $parameter->{host} : $anvil->Get->short_host_name; + my $host = defined $parameter->{host} ? $parameter->{host} : ""; my $target = $anvil->Get->short_host_name(); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { anvil_uuid => $anvil_uuid, @@ -1395,6 +1395,12 @@ sub parse_definition host => $host, }}); + if (not $target) + { + $target = $anvil->Get->short_host_name; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { target => $target }}); + } + if (not $server) { $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Server->parse_definition()", parameter => "server" }}); @@ -1805,6 +1811,16 @@ sub parse_definition "server::${target}::${server}::${source}::device::${device}::target::${device_target}::type" => $anvil->data->{server}{$target}{$server}{$source}{device}{$device}{target}{$device_target}{type}, }}); + if (($boot_order) && ($boot_order =~ /^\d+$/)) + { + $anvil->data->{server}{$target}{$server}{$source}{boot_order}{$boot_order}{device_target} = $device_target; + $anvil->data->{server}{$target}{$server}{$source}{boot_order}{$boot_order}{device_type} = $device; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "server::${target}::${server}::${source}::boot_order::${boot_order}::device_target" => $anvil->data->{server}{$target}{$server}{$source}{boot_order}{$boot_order}{device_target}, + "server::${target}::${server}::${source}::boot_order::${boot_order}::device_type" => $anvil->data->{server}{$target}{$server}{$source}{boot_order}{$boot_order}{device_type}, + }}); + } + # Record type-specific data if ($device eq "disk") { diff --git a/man/Makefile.am b/man/Makefile.am index d614bf43..401676d5 100644 --- a/man/Makefile.am +++ b/man/Makefile.am @@ -19,6 +19,7 @@ dist_man8_MANS = \ anvil-manage-files.8 \ anvil-manage-keys.1 \ anvil-manage-server.8 \ + anvil-manage-server-storage.8 \ anvil-manage-storage-groups.8 \ scancore.8 \ striker-initialize-host.8 diff --git a/man/anvil-manage-server-storage.8 b/man/anvil-manage-server-storage.8 new file mode 100644 index 00000000..e69de29b diff --git a/scancore-agents/scan-drbd/scan-drbd b/scancore-agents/scan-drbd/scan-drbd index 8307ba6c..69cdb8e6 100755 --- a/scancore-agents/scan-drbd/scan-drbd +++ b/scancore-agents/scan-drbd/scan-drbd @@ -914,6 +914,16 @@ sub process_volumes "s4:old_scan_drbd_volume_size" => $old_scan_drbd_volume_size." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $old_scan_drbd_volume_size}).")", }}); + # If the $new_scan_drbd_volume_size is '0', the device is down. Don't update it so we can + # recall the last known size. + if ($new_scan_drbd_volume_size == 0) + { + $new_scan_drbd_volume_size = $old_scan_drbd_volume_size; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + new_scan_drbd_volume_size => $new_scan_drbd_volume_size." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $new_scan_drbd_volume_size}).")", + }}); + } + my $update = 0; if ($new_scan_drbd_volume_device_path ne $old_scan_drbd_volume_device_path) { diff --git a/tools/Makefile.am b/tools/Makefile.am index e0e2b7ea..96f7b2e1 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -26,6 +26,7 @@ dist_sbin_SCRIPTS = \ anvil-manage-keys \ anvil-manage-power \ anvil-manage-server \ + anvil-manage-server-storage \ anvil-manage-storage-groups \ anvil-migrate-server \ anvil-network-profiler \ @@ -76,4 +77,3 @@ sharedir = ${datarootdir}/anvil dist_share_DATA = striker-auto-initialize-all.example -# -rwxr-xr-x. 1 digimer digimer 34K Feb 1 2020 tool-fio-tester diff --git a/tools/anvil-daemon b/tools/anvil-daemon index 9b20a131..bcd15b75 100755 --- a/tools/anvil-daemon +++ b/tools/anvil-daemon @@ -766,7 +766,7 @@ sub check_db_in_use_states my ($anvil) = @_; # We only reap db_in_use entries for us. - $anvil->System->pids(); + $anvil->System->pids({debug => 2}); my $query = " SELECT state_uuid, diff --git a/tools/anvil-manage-server-storage b/tools/anvil-manage-server-storage new file mode 100755 index 00000000..06eeaf66 --- /dev/null +++ b/tools/anvil-manage-server-storage @@ -0,0 +1,398 @@ +#!/usr/bin/perl +# +# This program will manage servers; Changing RAM, CPU cores, Growing virtual disks, adding virtual disks, +# inserting and ejecting ISO images into virtual optical media. +# +# Exit codes; +# 0 = Normal exit. +# 1 = No database connection. +# +# TODO: +# + +use strict; +use warnings; +use Anvil::Tools; +require POSIX; +use Term::Cap; +use Data::Dumper; + +my $THIS_FILE = ($0 =~ /^.*\/(.*)$/)[0]; +my $running_directory = ($0 =~ /^(.*?)\/$THIS_FILE$/)[0]; +if (($running_directory =~ /^\./) && ($ENV{PWD})) +{ + $running_directory =~ s/^\./$ENV{PWD}/; +} + +# Turn off buffering so that the pinwheel will display while waiting for the SSH call(s) to complete. +$| = 1; + +my $anvil = Anvil::Tools->new(); + +### TODO: Remove this before final release +$anvil->Log->level({set => 2}); +$anvil->Log->secure({set => 1}); +########################################## + +# Read switches (target ([user@]host[:port]) and the file with the target's password. +$anvil->Get->switches({list => [ + "add", + "anvil", + "grow", + "server", + ], man => $THIS_FILE}); +$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => $anvil->data->{switches}}); +$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0115", variables => { program => $THIS_FILE }}); + +# Connect to the database(s). If we have no connections, we'll proceed anyway as one of the 'run_once' tasks +# is to setup the database server. +$anvil->Database->connect(); +$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 3, secure => 0, key => "log_0132"}); +if (not $anvil->data->{sys}{database}{connections}) +{ + # No databases, update the job, sleep for a bit and then exit. The daemon will pick it up and try + # again after we exit. + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, priority => "err", key => "error_0305"}); + sleep 10; + $anvil->nice_exit({exit_code => 1}); +} + +# If we don't have a job UUID, try to find one. +if (not $anvil->data->{switches}{'job-uuid'}) +{ + # Load the job data. + $anvil->data->{switches}{'job-uuid'} = $anvil->Job->get_job_uuid({program => $THIS_FILE}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { "switches::job-uuid" => $anvil->data->{switches}{'job-uuid'} }}); +} + +$anvil->Database->get_hosts(); +$anvil->Database->get_anvils(); +$anvil->Database->get_servers(); + +if ($anvil->data->{switches}{anvil}) +{ + # Make sure they asked for a real anvil. + $anvil->Get->anvil_from_switch({string => $anvil->data->{switches}{anvil}}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + "switches::anvil_name" => $anvil->data->{switches}{anvil_name}, + "switches::anvil_uuid" => $anvil->data->{switches}{anvil_uuid}, + }}); +} + +if (not $anvil->data->{switches}{server}) +{ + # Show the list of servers. + show_server_list($anvil); + print "\nPlease specify which server you want to modify using '--server '.\n\n"; + $anvil->nice_exit({exit_code => 0}); +} +manage_server($anvil); + +$anvil->nice_exit({exit_code => 0}); + + +############################################################################################################# +# Functions # +############################################################################################################# + +sub manage_server +{ + my ($anvil) = @_; + + $anvil->Get->server_from_switch({ + debug => 2, + string => $anvil->data->{switches}{server}, + anvil_uuid => $anvil->data->{switches}{anvil_uuid}, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + "switches::server_name" => $anvil->data->{switches}{server_name}, + "switches::server_uuid" => $anvil->data->{switches}{server_uuid}, + }}); + + if (not $anvil->data->{switches}{server_uuid}) + { + show_server_list($anvil); + if ($anvil->data->{switches}{anvil_uuid}) + { + # Not found on the requested Anvil! node. + print "\nThe server: [".$anvil->data->{switches}{server}."] was not found on the Anvil! node: [".$anvil->data->{switches}{anvil_name}."]. Valid servers are above.\n\n"; + } + else + { + # Not found at all. + print "\nThe server: [".$anvil->data->{switches}{server}."] was not found. Valid servers are above.\n\n"; + } + $anvil->nice_exit({exit_code => 1}); + } + + print "Working with the server: [".$anvil->data->{switches}{server_name}."], UUID: [".$anvil->data->{switches}{server_uuid}."]\n"; + my $short_host_name = $anvil->Get->short_host_name; + my $server_name = $anvil->data->{switches}{server_name}; + my $server_uuid = $anvil->data->{switches}{server_uuid}; + my $server_definition = $anvil->data->{servers}{server_uuid}{$server_uuid}{server_definition_xml}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + 's1:short_host_name' => $short_host_name, + 's2:server_name' => $server_name, + 's3:server_uuid' => $server_uuid, + 's4:server_definition' => $server_definition, + }}); + + # Parse the definition. + $anvil->Server->parse_definition({ + debug => 3, + host => $short_host_name, + server => $server_name, + source => "from_virsh", + definition => $server_definition, + }); + + #$anvil->data->{server}{$short_host_name}{$server_name}{from_virsh}{boot_order} + foreach my $device ("disk", "cdrom") + { + if ($device eq "disk") + { + print "\nDisk Drives:\n"; + } + else + { + print "\nOptical Drives:\n"; + } + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { device => $device }}); + next if $device ne "cdrom" && $device ne "disk"; + foreach my $device_target (sort {$a cmp $b} keys %{$anvil->data->{server}{$short_host_name}{$server_name}{from_virsh}{device}{$device}{target}}) + { + my $alias = $anvil->data->{server}{$short_host_name}{$server_name}{from_virsh}{device}{$device}{target}{$device_target}{alias}; + my $boot_order = $anvil->data->{server}{$short_host_name}{$server_name}{from_virsh}{device}{$device}{target}{$device_target}{boot_order}; + my $say_boot = $boot_order eq "99" ? "--" : sprintf("%02d", $boot_order); + my $type = $anvil->data->{server}{$short_host_name}{$server_name}{from_virsh}{device}{$device}{target}{$device_target}{type}; + my $address_type = $anvil->data->{server}{$short_host_name}{$server_name}{from_virsh}{device}{$device}{target}{$device_target}{address}{type}; + my $address_bus = $anvil->data->{server}{$short_host_name}{$server_name}{from_virsh}{device}{$device}{target}{$device_target}{address}{bus}; + my $driver_name = $anvil->data->{server}{$short_host_name}{$server_name}{from_virsh}{device}{$device}{target}{$device_target}{driver}{name}; + my $device_bus = $anvil->data->{server}{$short_host_name}{$server_name}{from_virsh}{device}{$device}{target}{$device_target}{device_bus}; + my $driver_type = $anvil->data->{server}{$short_host_name}{$server_name}{from_virsh}{device}{$device}{target}{$device_target}{driver}{type}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + 's01:device_target' => $device_target, + 's02:alias' => $alias, + 's03:boot_order' => $boot_order, + 's04:say_boot' => $say_boot, + 's05:type' => $type, + 's06:address_type' => $address_type, + 's07:address_bus' => $address_bus, + 's08:driver_name' => $driver_name, + 's09:device_bus' => $device_bus, + 's10:driver_type' => $driver_type, + }}); + if ($device eq "disk") + { + my $address_domain = $anvil->data->{server}{$short_host_name}{$server_name}{from_virsh}{device}{$device}{target}{$device_target}{address}{domain}; + my $address_slot = $anvil->data->{server}{$short_host_name}{$server_name}{from_virsh}{device}{$device}{target}{$device_target}{address}{slot}; + my $address_function = $anvil->data->{server}{$short_host_name}{$server_name}{from_virsh}{device}{$device}{target}{$device_target}{address}{function}; + my $device_path = $anvil->data->{server}{$short_host_name}{$server_name}{from_virsh}{device}{$device}{target}{$device_target}{path}; + my $driver_io = $anvil->data->{server}{$short_host_name}{$server_name}{from_virsh}{device}{$device}{target}{$device_target}{driver}{io}; + my $driver_cache = $anvil->data->{server}{$short_host_name}{$server_name}{from_virsh}{device}{$device}{target}{$device_target}{driver}{cache}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + 's1:address_domain' => $address_domain, + 's2:address_slot' => $address_slot, + 's3:address_function' => $address_function, + 's4:device_path' => $device_path, + 's5:driver_io' => $driver_io, + 's6:driver_cache' => $driver_cache, + }}); + print "- Target: [".$device_target."], boot: [".$say_boot."], path: [".$device_path."], cache: [".$driver_cache."], driver type: [".$driver_type."]\n"; + } + else + { + my $address_controller = $anvil->data->{server}{$short_host_name}{$server_name}{from_virsh}{device}{$device}{target}{$device_target}{address}{controller}; + my $address_unit = $anvil->data->{server}{$short_host_name}{$server_name}{from_virsh}{device}{$device}{target}{$device_target}{address}{unit}; + my $address_target = $anvil->data->{server}{$short_host_name}{$server_name}{from_virsh}{device}{$device}{target}{$device_target}{address}{target}; + my $device_path = $anvil->data->{server}{$short_host_name}{$server_name}{from_virsh}{device}{$device}{target}{$device_target}{path}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + 's1:address_controller' => $address_controller, + 's2:address_unit' => $address_unit, + 's3:address_target' => $address_target, + 's4:device_path' => $device_path, + }}); + print "- Target: [".$device_target."], boot: [".$say_boot."], ISO: [".$device_path."]\n"; + } + } + } + print "\n"; + + my $drbd_resource = ""; + foreach my $device_path (sort {$a cmp $b} keys %{$anvil->data->{server}{$short_host_name}{$server_name}{device}}) + { + my $on_lv = $anvil->data->{server}{$short_host_name}{$server_name}{device}{$device_path}{on_lv}; + $drbd_resource = $anvil->data->{server}{$short_host_name}{$server_name}{device}{$device_path}{resource}; + my $device_target = $anvil->data->{server}{$short_host_name}{$server_name}{device}{$device_path}{target}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + 's1:device_path' => $device_path, + 's2:on_lv' => $on_lv, + 's3:drbd_resource' => $drbd_resource, + 's4:device_target' => $device_target, + }}); + } + foreach my $drbd_resource (sort {$a cmp $b} keys %{$anvil->data->{server}{$short_host_name}{$server_name}{drbd}{resource}}) + { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { drbd_resource => $drbd_resource }}); + } + + # Get the DRBD volume data + # Get the DRBD volume data + my $query = " +SELECT + a.host_uuid, + b.scan_drbd_resource_xml, + c.scan_drbd_volume_number, + c.scan_drbd_volume_device_path, + c.scan_drbd_volume_device_minor, + c.scan_drbd_volume_size +FROM + hosts a, + scan_drbd_resources b, + scan_drbd_volumes c +WHERE + a.host_uuid = b.scan_drbd_resource_host_uuid +AND + b.scan_drbd_resource_uuid = c.scan_drbd_volume_scan_drbd_resource_uuid +AND + b.scan_drbd_resource_name = ".$anvil->Database->quote($drbd_resource)." +;"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }}); + + my $results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__}); + my $count = @{$results}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + results => $results, + count => $count, + }}); + foreach my $row (@{$results}) + { + my $host_uuid = $row->[0]; + my $short_host_name = $anvil->data->{hosts}{host_uuid}{$host_uuid}{short_host_name}; + my $host_type = $anvil->data->{hosts}{host_uuid}{$host_uuid}{host_type}; + my $scan_drbd_volume_number = $row->[2]; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + 's1:host_uuid' => $host_uuid, + 's2:host_type' => $host_type, + 's3:short_host_name' => $short_host_name, + 's4:scan_drbd_volume_number' => $scan_drbd_volume_number, + }}); + + $anvil->data->{drbd_resource}{$drbd_resource}{host_uuid}{$host_uuid}{volume_number}{$scan_drbd_volume_number}{device_path} = $row->[3]; + $anvil->data->{drbd_resource}{$drbd_resource}{host_uuid}{$host_uuid}{volume_number}{$scan_drbd_volume_number}{device_minor} = $row->[4]; + $anvil->data->{drbd_resource}{$drbd_resource}{host_uuid}{$host_uuid}{volume_number}{$scan_drbd_volume_number}{volume_size} = $row->[5]; + $anvil->data->{drbd_resource}{$drbd_resource}{host_type}{$host_type}{short_host_name}{$short_host_name}{host_uuid} = $host_uuid; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + "s1:drbd_resource::${drbd_resource}::host_uuid::${host_uuid}::volume_number::${scan_drbd_volume_number}::device_path" => $anvil->data->{drbd_resource}{$drbd_resource}{host_uuid}{$host_uuid}{volume_number}{$scan_drbd_volume_number}{device_path}, + "s2:drbd_resource::${drbd_resource}::host_uuid::${host_uuid}::volume_number::${scan_drbd_volume_number}::device_minor" => $anvil->data->{drbd_resource}{$drbd_resource}{host_uuid}{$host_uuid}{volume_number}{$scan_drbd_volume_number}{device_minor}, + "s3:drbd_resource::${drbd_resource}::host_uuid::${host_uuid}::volume_number::${scan_drbd_volume_number}::volume_size" => $anvil->data->{drbd_resource}{$drbd_resource}{host_uuid}{$host_uuid}{volume_number}{$scan_drbd_volume_number}{volume_size}, + "s4:drbd_resource::${drbd_resource}::host_type::${host_type}::short_host_name::${short_host_name}::host_uuid" => $anvil->data->{drbd_resource}{$drbd_resource}{host_type}{$host_type}{short_host_name}{$short_host_name}{host_uuid}, + }}); + + if (not exists $anvil->data->{drbd_resource}{$drbd_resource}{xml}) + { + $anvil->data->{drbd_resource}{$drbd_resource}{xml} = $row->[1]; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + "drbd_resource::${drbd_resource}::xml" => $anvil->data->{drbd_resource}{$drbd_resource}{xml}, + }}); + $anvil->DRBD->parse_resource({ + debug => 2, + xml => $anvil->data->{drbd_resource}{$drbd_resource}{xml}, + }); + } + } + + print "Sub-Nodes:\n"; + show_volume($anvil, $drbd_resource, "node"); + + my $dr_count = keys %{$anvil->data->{drbd_resource}{$drbd_resource}{host_type}{dr}{short_host_name}}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { dr_count => $dr_count }}); + if ($dr_count) + { + print "DR Hosts:\n"; + show_volume($anvil, $drbd_resource, "dr"); + } + + return(0); +} + +sub show_volume +{ + my ($anvil, $drbd_resource, $host_type) = @_; + + foreach my $short_host_name (sort {$a cmp $b} keys %{$anvil->data->{drbd_resource}{$drbd_resource}{host_type}{$host_type}{short_host_name}}) + { + my $host_uuid = $anvil->data->{drbd_resource}{$drbd_resource}{host_type}{$host_type}{short_host_name}{$short_host_name}{host_uuid}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + 's1:short_host_name' => $short_host_name, + 's2:host_uuid' => $host_uuid, + }}); + print " |- Name: [".$short_host_name."], UUID: [".$host_uuid."]\n"; + foreach my $volume_number (sort {$a cmp $b} keys %{$anvil->data->{drbd_resource}{$drbd_resource}{host_uuid}{$host_uuid}{volume_number}}) + { + my $device_path = $anvil->data->{drbd_resource}{$drbd_resource}{host_uuid}{$host_uuid}{volume_number}{$volume_number}{device_path}; + my $device_minor = $anvil->data->{drbd_resource}{$drbd_resource}{host_uuid}{$host_uuid}{volume_number}{$volume_number}{device_minor}; + my $volume_size = $anvil->data->{drbd_resource}{$drbd_resource}{host_uuid}{$host_uuid}{volume_number}{$volume_number}{volume_size}; + my $backing_disk = $anvil->data->{new}{resource}{$drbd_resource}{host_uuid}{$host_uuid}{volume_number}{$volume_number}{backing_disk}; + my $meta_disk = $anvil->data->{new}{resource}{$drbd_resource}{host_uuid}{$host_uuid}{volume_number}{$volume_number}{meta_disk}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + 's1:volume_number' => $volume_number, + 's2:device_path' => $device_path, + 's3:device_minor' => $device_minor, + 's4:volume_size' => $volume_size, + 's5:backing_disk' => $backing_disk, + 's6:meta_disk' => $meta_disk, + }}); + print " ^- Volume: [".$volume_number."], backing device: [".$backing_disk."], DRBD minor: [".$device_minor."], size: [".$anvil->Convert->bytes_to_human_readable({'bytes' => $volume_size})."]\n"; + } + } + + return(0); +} + +sub show_server_list +{ + my ($anvil) = @_; + + # Loop through all Anvil! nodes, then all server in that Anvil! + foreach my $anvil_name (sort {$a cmp $b} keys %{$anvil->data->{anvils}{anvil_name}}) + { + my $anvil_uuid = $anvil->data->{anvils}{anvil_name}{$anvil_name}{anvil_uuid}; + my $anvil_description = $anvil->data->{anvils}{anvil_name}{$anvil_name}{anvil_description}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + anvil_name => $anvil_name, + anvil_uuid => $anvil_uuid, + anvil_description => $anvil_description, + }}); + if (($anvil->data->{switches}{anvil_uuid}) && ($anvil->data->{switches}{anvil_uuid} ne $anvil_uuid)) + { + next; + } + print "\nAnvil! Node: [".$anvil_name."], UUID: [".$anvil_uuid."] - Description: [".$anvil_description."]\n"; + + my $server_count = 0; + if (exists $anvil->data->{servers}{anvil_uuid}{$anvil_uuid}{server_name}) + { + $server_count = keys %{$anvil->data->{servers}{anvil_uuid}{$anvil_uuid}{server_name}}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { server_count => $server_count }}); + } + if (not $server_count) + { + print "- No servers on this node yet\n"; + } + else + { + foreach my $server_name (sort {$a cmp $b} keys %{$anvil->data->{servers}{anvil_uuid}{$anvil_uuid}{server_name}}) + { + my $server_uuid = $anvil->data->{servers}{anvil_uuid}{$anvil_uuid}{server_name}{$server_name}{server_uuid}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + server_name => $server_name, + server_uuid => $server_uuid, + }}); + print "^- Server: [".$server_name."], UUID: [".$server_uuid."]\n"; + } + } + } + + return(0); +}