This adds support (testing needed) for "Long-Throw" DR; which is a wrapper for using 'drbd-proxy' to provide larger transmit buffers so slow/high-latency DR hosts.

* Created DRBD->check_proxy_license() to do (some level of) sanity checks on the DRBD proxy license file.
* Updated DRBD->gather_data() to parse out the inside and outside ports for resource configs using proxy.
* Reworked DRBD->get_next_resource() to return 1, 3 or 7 TCP ports depending, with the new long_throw_ports parameter triggering the 7 ports.
* Added 'tcpdump' to the anvil-core requires list.
* Reworked scan-drbd to record the ports used in proxy configs. This required adding a check to change the 'scan_drbd_peer_tcp_port' column type to 'text' to support CSVs.
* Reworked anvil-manage-dr (needs testing!) to support "long-throw" DR configs.
* Updated anvil-safe-stop to check if the nodes are in the cluster before trying to migrate.

Signed-off-by: Digimer <digimer@alteeve.ca>
main
Digimer 2 years ago
parent c8ee75420d
commit 2fab7bc1b7
  1. 1
      Anvil/Tools.pm
  2. 399
      Anvil/Tools/DRBD.pm
  3. 1
      anvil.spec.in
  4. 60
      scancore-agents/scan-drbd/scan-drbd
  5. 4
      scancore-agents/scan-drbd/scan-drbd.sql
  6. 40
      share/words.xml
  7. 183
      tools/anvil-manage-dr
  8. 2
      tools/anvil-provision-server
  9. 61
      tools/anvil-safe-stop

@ -1043,6 +1043,7 @@ sub _set_paths
'corosync.conf' => "/etc/corosync/corosync.conf", 'corosync.conf' => "/etc/corosync/corosync.conf",
'dhcpd.conf' => "/etc/dhcp/dhcpd.conf", 'dhcpd.conf' => "/etc/dhcp/dhcpd.conf",
'dnf.conf' => "/etc/dnf/dnf.conf", 'dnf.conf' => "/etc/dnf/dnf.conf",
'drbd-proxy.license' => "/etc/drbd-proxy.license",
'firewalld.conf' => "/etc/firewalld/firewalld.conf", 'firewalld.conf' => "/etc/firewalld/firewalld.conf",
'global-common.conf' => "/etc/drbd.d/global_common.conf", 'global-common.conf' => "/etc/drbd.d/global_common.conf",
hostname => "/etc/hostname", hostname => "/etc/hostname",

@ -17,6 +17,7 @@ my $THIS_FILE = "DRBD.pm";
# allow_two_primaries # allow_two_primaries
# check_if_syncsource # check_if_syncsource
# check_if_synctarget # check_if_synctarget
# check_proxy_license
# delete_resource # delete_resource
# gather_data # gather_data
# get_devices # get_devices
@ -347,6 +348,144 @@ sub check_if_synctarget
} }
=head2 check_proxy_license
This method checks to see if the DRBD Proxy license file exists and _appears_ correct. If things look good, C<< 0 >> is returned. If there is a problem, C<< 1 >> is returned.
=cut
sub check_proxy_license
{
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->check_proxy_license()" }});
if (not -e $anvil->data->{path}{configs}{'drbd-proxy.license'})
{
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0728"});
return(1);
}
# Read in the file.
my $wildcard_mac = 0;
my $problem = 0;
my $owner = "";
my $expiry_date = 0;
my $mac_addresses = [];
my $features = "";
my $signature = "";
my $license_body = $anvil->Storage->read_file({file => $anvil->data->{path}{configs}{'drbd-proxy.license'}});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { license_body => $license_body }});
foreach my $line (split/\n/, $license_body)
{
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }});
if ($line =~ /^owner: (.*)$/)
{
$owner = $1;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { owner => $owner }});
next;
}
if ($line =~ /^expiry-date: (.*)$/)
{
$expiry_date = $1;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { expiry_date => $expiry_date }});
next;
}
if ($line =~ /^mac-address: (.*)$/)
{
my $this_mac = $1;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { this_mac => $this_mac }});
if ($this_mac eq "00:00:00:00:00:00")
{
$wildcard_mac = 1;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { wildcard_mac => $wildcard_mac }});
}
push @{$mac_addresses}, $this_mac;
next;
}
if ($line =~ /^features: (.*)$/)
{
$features = $1;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { features => $features }});
next;
}
if ($line =~ /^signature: (.*)$/)
{
$signature = $1;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { signature => $signature }});
next;
}
}
if ((not $owner) or
(not $expiry_date) or
(not $signature))
{
# Appears to not be a valid license file.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0731"});
return(1);
}
if (time >= $expiry_date)
{
# The license has expired.
$problem = 1;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0729"});
}
if (not $wildcard_mac)
{
# Loop through all MACs on this system and see if one matches the license.
my $match = 0;
my $host = $anvil->Get->short_host_name();
my $mac_count = @{$mac_addresses};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
host => $host,
mac_count => $mac_count,
}});
if (not $mac_count)
{
}
foreach my $in_iface (sort {$a cmp $b} keys %{$anvil->data->{network}{$host}{interface}})
{
my $mac_address = $anvil->data->{network}{$host}{interface}{$in_iface}{mac_address};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
's1:in_iface' => $in_iface,
's2:mac_address' => $mac_address,
}});
foreach my $licensed_mac (@{$mac_addresses})
{
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { licensed_mac => $licensed_mac }});
if (lc($mac_address) eq lc($licensed_mac))
{
$match = 1;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { match => $match }});
last;
}
}
last if $match;
}
if (not $match)
{
# MACs don't match.
$problem = 1;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0730"});
}
}
return($problem);
}
=head2 delete_resource =head2 delete_resource
This method deletes an entire resource. It does this by looping through the volumes configured in a resource and deleting them one after the other (even if there is only one volume). This method deletes an entire resource. It does this by looping through the volumes configured in a resource and deleting them one after the other (even if there is only one volume).
@ -578,6 +717,13 @@ sub gather_data
} }
} }
my $local_host_name = $anvil->Get->host_name;
my $local_short_host_name = $anvil->Get->short_host_name;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
local_host_name => $local_host_name,
local_short_host_name => $local_short_host_name,
}});
local $@; local $@;
my $dom = eval { XML::LibXML->load_xml(string => $xml); }; my $dom = eval { XML::LibXML->load_xml(string => $xml); };
if ($@) if ($@)
@ -598,13 +744,6 @@ sub gather_data
$anvil->data->{new}{scan_drbd}{scan_drbd_flush_md} = 1; $anvil->data->{new}{scan_drbd}{scan_drbd_flush_md} = 1;
$anvil->data->{new}{scan_drbd}{scan_drbd_timeout} = 6; # Default is '60', 6 seconds $anvil->data->{new}{scan_drbd}{scan_drbd_timeout} = 6; # Default is '60', 6 seconds
$anvil->data->{new}{scan_drbd}{scan_drbd_total_sync_speed} = 0; $anvil->data->{new}{scan_drbd}{scan_drbd_total_sync_speed} = 0;
my $local_host_name = $anvil->Get->host_name;
my $local_short_host_name = $anvil->Get->short_host_name;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
local_host_name => $local_host_name,
local_short_host_name => $local_short_host_name,
}});
foreach my $name ($dom->findnodes('/config/common/section')) foreach my $name ($dom->findnodes('/config/common/section'))
{ {
my $section = $name->{name}; my $section = $name->{name};
@ -755,6 +894,37 @@ sub gather_data
"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}, "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}, "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},
}});
}
} }
# $peer = $this_host_name; # $peer = $this_host_name;
@ -1017,6 +1187,127 @@ sub gather_data
"new::scan_drbd::scan_drbd_total_sync_speed" => $anvil->data->{new}{scan_drbd}{scan_drbd_total_sync_speed}." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{new}{scan_drbd}{scan_drbd_total_sync_speed}}).")", "new::scan_drbd::scan_drbd_total_sync_speed" => $anvil->data->{new}{scan_drbd}{scan_drbd_total_sync_speed}." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{new}{scan_drbd}{scan_drbd_total_sync_speed}}).")",
}}); }});
# For resources using drbd-proxy, the host1_to_host2 will be using the internal IP, which we want to
# switch to the 'outside' IP. Also, the ports will need to be concatenated together as a CSV list.
foreach my $resource (sort {$a cmp $b} keys %{$anvil->data->{new}{resource}})
{
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { resource => $resource }});
foreach my $host1_name (sort {$a cmp $b} keys %{$anvil->data->{new}{resource}{$resource}{host1_to_host2}})
{
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { host1_name => $host1_name }});
foreach my $host2_name (sort {$a cmp $b} keys %{$anvil->data->{new}{resource}{$resource}{host1_to_host2}{$host1_name}})
{
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { host2_name => $host2_name }});
my $host1_ip_address = $anvil->data->{new}{resource}{$resource}{host1_to_host2}{$host1_name}{$host2_name}{host1_ip_address};
my $host1_tcp_port = $anvil->data->{new}{resource}{$resource}{host1_to_host2}{$host1_name}{$host2_name}{host1_tcp_port};
my $host2_ip_address = $anvil->data->{new}{resource}{$resource}{host1_to_host2}{$host1_name}{$host2_name}{host2_ip_address};
my $host2_tcp_port = $anvil->data->{new}{resource}{$resource}{host1_to_host2}{$host1_name}{$host2_name}{host2_tcp_port};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"s1:host1_ip_address" => $host1_ip_address,
"s2:host1_tcp_port" => $host1_tcp_port,
"s3:host2_ip_address" => $host2_ip_address,
"s4:host2_tcp_port" => $host2_tcp_port,
}});
my $host1_tcp_ports = $host1_tcp_port.",";
my $host2_tcp_ports = $host2_tcp_port.",";
my $proxy_found = 0;
if (exists $anvil->data->{new}{resource}{$resource}{proxy}{$host1_name})
{
$proxy_found = 1;
my $host1_inside_ip_address = $anvil->data->{new}{resource}{$resource}{proxy}{$host1_name}{inside}{ip_address};
my $host1_inside_tcp_port = $anvil->data->{new}{resource}{$resource}{proxy}{$host1_name}{inside}{tcp_port};
my $host1_outside_ip_address = $anvil->data->{new}{resource}{$resource}{proxy}{$host1_name}{outside}{ip_address};
my $host1_outside_tcp_port = $anvil->data->{new}{resource}{$resource}{proxy}{$host1_name}{outside}{tcp_port};
$host1_tcp_ports .= $host1_inside_tcp_port.",".$host1_outside_tcp_port;
$host1_ip_address = $host1_outside_ip_address;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"s1:proxy_found" => $proxy_found,
"s2:host1_inside_ip_address" => $host1_inside_ip_address,
"s3:host1_inside_tcp_port" => $host1_inside_tcp_port,
"s4:host1_outside_ip_address" => $host1_outside_ip_address,
"s5:host1_outside_tcp_port" => $host1_outside_tcp_port,
"s6:host1_tcp_ports" => $host1_tcp_ports,
"s7:host1_ip_address" => $host1_ip_address,
}});
}
if (exists $anvil->data->{new}{resource}{$resource}{proxy}{$host2_name})
{
$proxy_found = 1;
my $host2_inside_ip_address = $anvil->data->{new}{resource}{$resource}{proxy}{$host2_name}{inside}{ip_address};
my $host2_inside_tcp_port = $anvil->data->{new}{resource}{$resource}{proxy}{$host2_name}{inside}{tcp_port};
my $host2_outside_ip_address = $anvil->data->{new}{resource}{$resource}{proxy}{$host2_name}{outside}{ip_address};
my $host2_outside_tcp_port = $anvil->data->{new}{resource}{$resource}{proxy}{$host2_name}{outside}{tcp_port};
$host2_tcp_ports .= $host2_inside_tcp_port.",".$host2_outside_tcp_port;
$host2_ip_address = $host2_outside_ip_address;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"s1:proxy_found" => $proxy_found,
"s2:host2_inside_ip_address" => $host2_inside_ip_address,
"s3:host2_inside_tcp_port" => $host2_inside_tcp_port,
"s4:host2_outside_ip_address" => $host2_outside_ip_address,
"s5:host2_outside_tcp_port" => $host2_outside_tcp_port,
"s6:host2_tcp_ports" => $host2_tcp_ports,
"s7:host2_ip_address" => $host2_ip_address,
}});
}
next if not $proxy_found;
# Save the new info.
$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_ports;
$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_ports;
$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},
}});
my $peer = "";
if (($host1_name eq $local_short_host_name) or ($host1_name eq $local_host_name))
{
# Our peer is host2. Is the protocol C? If so, this can't be proxy.
$peer = $host2_name;
my $peer_protocol = $anvil->data->{new}{resource}{$resource}{peer}{$peer}{protocol};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"s1:peer" => $peer,
"s2:peer_protocol" => $peer_protocol,
}});
next if uc($peer_protocol) eq "C";
# Still here? Then this is a proxy connection, update the ports and IP
$anvil->data->{new}{resource}{$resource}{peer}{$peer}{peer_ip_address} = $host2_ip_address;
$anvil->data->{new}{resource}{$resource}{peer}{$peer}{tcp_port} = $host1_tcp_ports; # Store our ports.
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"s1:new::resource::${resource}::peer::${peer}::peer_ip_address" => $anvil->data->{new}{resource}{$resource}{peer}{$peer}{peer_ip_address},
"s2:new::resource::${resource}::peer::${peer}::tcp_port" => $anvil->data->{new}{resource}{$resource}{peer}{$peer}{tcp_port},
}});
}
else
{
# Our peer is host1, Is the protocol C? If so, this can't be proxy.
$peer = $host1_name;
my $peer_protocol = $anvil->data->{new}{resource}{$resource}{peer}{$peer}{protocol};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"s1:peer" => $peer,
"s2:peer_protocol" => $peer_protocol,
}});
next if uc($peer_protocol) eq "C";
# Still here? Then this is a proxy connection, update the ports and IP
$anvil->data->{new}{resource}{$resource}{peer}{$peer}{peer_ip_address} = $host1_ip_address;
$anvil->data->{new}{resource}{$resource}{peer}{$peer}{tcp_port} = $host2_tcp_ports; # Store out ports
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"s1:new::resource::${resource}::peer::${peer}::peer_ip_address" => $anvil->data->{new}{resource}{$resource}{peer}{$peer}{peer_ip_address},
"s2:new::resource::${resource}::peer::${peer}::tcp_port" => $anvil->data->{new}{resource}{$resource}{peer}{$peer}{tcp_port},
}});
}
}
}
}
return(0); return(0);
} }
@ -1399,6 +1690,10 @@ This is the Anvil! in which we're looking for the next free resources. It's requ
If set, the 'free_port' returned will be a comma-separated pair of TCP ports. This is meant to help find two TCP ports needed to connect a resource from both nodes to a DR host. If set, the 'free_port' returned will be a comma-separated pair of TCP ports. This is meant to help find two TCP ports needed to connect a resource from both nodes to a DR host.
=head3 long_throw_ports (optional, default '0')
If set, the 'free_port' returned will be a comma-separated list of seven TCP ports needed for a full B<< Long Throw >> configuration.
=head3 resource_name (optional) =head3 resource_name (optional)
If this is set, and the resource is found to already exist, the first DRBD minor number and first used TCP port are returned. Alternatively, if C<< force_unique >> is set to C<< 1 >>, and the resource is found to exist, empty strings are returned. If this is set, and the resource is found to already exist, the first DRBD minor number and first used TCP port are returned. Alternatively, if C<< force_unique >> is set to C<< 1 >>, and the resource is found to exist, empty strings are returned.
@ -1416,13 +1711,18 @@ sub get_next_resource
my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "DRBD->get_next_resource()" }}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "DRBD->get_next_resource()" }});
### TODO: Cache results in the states or variables table and don't reuse ports given out for five
### minutes. If the user batches a series of calls, TCP ports / minor numbers could be offered
### multiple times.
my $anvil_uuid = defined $parameter->{anvil_uuid} ? $parameter->{anvil_uuid} : ""; my $anvil_uuid = defined $parameter->{anvil_uuid} ? $parameter->{anvil_uuid} : "";
my $dr_tcp_ports = defined $parameter->{dr_tcp_ports} ? $parameter->{dr_tcp_ports} : ""; my $dr_tcp_ports = defined $parameter->{dr_tcp_ports} ? $parameter->{dr_tcp_ports} : "";
my $long_throw_ports = defined $parameter->{long_throw_ports} ? $parameter->{long_throw_ports} : "";
my $resource_name = defined $parameter->{resource_name} ? $parameter->{resource_name} : ""; my $resource_name = defined $parameter->{resource_name} ? $parameter->{resource_name} : "";
my $force_unique = defined $parameter->{force_unique} ? $parameter->{force_unique} : 0; my $force_unique = defined $parameter->{force_unique} ? $parameter->{force_unique} : 0;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
anvil_uuid => $anvil_uuid, anvil_uuid => $anvil_uuid,
dr_tcp_ports => $dr_tcp_ports, dr_tcp_ports => $dr_tcp_ports,
long_throw_ports => $long_throw_ports,
resource_name => $resource_name, resource_name => $resource_name,
force_unique => $force_unique, force_unique => $force_unique,
}}); }});
@ -1449,8 +1749,6 @@ sub get_next_resource
# Read in the resource information from both nodes. They _should_ be identical, but that's not 100% # Read in the resource information from both nodes. They _should_ be identical, but that's not 100%
# certain. # certain.
my $free_minor = "";
my $free_port = "";
my $node1_host_uuid = $anvil->data->{anvils}{anvil_uuid}{$anvil_uuid}{anvil_node1_host_uuid}; my $node1_host_uuid = $anvil->data->{anvils}{anvil_uuid}{$anvil_uuid}{anvil_node1_host_uuid};
my $node2_host_uuid = $anvil->data->{anvils}{anvil_uuid}{$anvil_uuid}{anvil_node2_host_uuid}; my $node2_host_uuid = $anvil->data->{anvils}{anvil_uuid}{$anvil_uuid}{anvil_node2_host_uuid};
my $dr1_host_uuid = $anvil->data->{anvils}{anvil_uuid}{$anvil_uuid}{anvil_dr1_host_uuid}; my $dr1_host_uuid = $anvil->data->{anvils}{anvil_uuid}{$anvil_uuid}{anvil_dr1_host_uuid};
@ -1545,12 +1843,20 @@ ORDER BY
}}); }});
$anvil->data->{drbd}{used_resources}{minor}{$scan_drbd_volume_device_minor}{used} = 1; $anvil->data->{drbd}{used_resources}{minor}{$scan_drbd_volume_device_minor}{used} = 1;
$anvil->data->{drbd}{used_resources}{tcp_port}{$scan_drbd_peer_tcp_port}{used} = 1;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"drbd::used_resources::minor::${scan_drbd_volume_device_minor}::used" => $anvil->data->{drbd}{used_resources}{minor}{$scan_drbd_volume_device_minor}{used}, "drbd::used_resources::minor::${scan_drbd_volume_device_minor}::used" => $anvil->data->{drbd}{used_resources}{minor}{$scan_drbd_volume_device_minor}{used},
"drbd::used_resources::tcp_port::${scan_drbd_peer_tcp_port}::used" => $anvil->data->{drbd}{used_resources}{tcp_port}{$scan_drbd_peer_tcp_port}{used},
}}); }});
# DRBD proxy uses three ports per connection. This handles that, and still works fine for
# single TCP ports.
foreach my $tcp_port (split/,/, $scan_drbd_peer_tcp_port)
{
$anvil->data->{drbd}{used_resources}{tcp_port}{$tcp_port}{used} = 1;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"drbd::used_resources::tcp_port::${tcp_port}::used" => $anvil->data->{drbd}{used_resources}{tcp_port}{$tcp_port}{used},
}});
}
if (($resource_name) && ($scan_drbd_resource_name eq $resource_name)) if (($resource_name) && ($scan_drbd_resource_name eq $resource_name))
{ {
# Found the resource the user was asking for. # Found the resource the user was asking for.
@ -1568,10 +1874,9 @@ ORDER BY
} }
} }
# If I'm here, I need to find the next free TCP port. We'll look for the next minor number for this # If I'm here, We'll look for the next minor number for this host.
# host.
my $looking = 1; my $looking = 1;
$free_minor = 0; my $free_minor = 0;
while($looking) while($looking)
{ {
if (exists $anvil->data->{drbd}{used_resources}{minor}{$free_minor}) if (exists $anvil->data->{drbd}{used_resources}{minor}{$free_minor})
@ -1586,60 +1891,62 @@ ORDER BY
} }
} }
# I need to find the next free TCP port.
$looking = 1; $looking = 1;
$free_port = 7788; my $check_port = 7788;
my $free_ports = "";
my $tcp_pair = ""; my $tcp_pair = "";
while($looking) my $proxy_list = "";
my $port_count = 0;
my $neeed_ports = 1;
if ($long_throw_ports)
{ {
if ((exists $anvil->data->{drbd}{used_resources}{tcp_port}{$free_port}) && $neeed_ports = 7;
($anvil->data->{drbd}{used_resources}{tcp_port}{$free_port}{used})) $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { neeed_ports => $neeed_ports }});
}
elsif ($dr_tcp_ports)
{ {
$free_port++; $neeed_ports = 3;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { free_port => $free_port }}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { neeed_ports => $neeed_ports }});
} }
else while($looking)
{ {
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { free_port => $free_port }}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { check_port => $check_port }});
if ($dr_tcp_ports) if ((exists $anvil->data->{drbd}{used_resources}{tcp_port}{$check_port}) &&
($anvil->data->{drbd}{used_resources}{tcp_port}{$check_port}{used}))
{ {
if (not $tcp_pair) $check_port++;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { check_port => $check_port }});
next;
}
else
{ {
$tcp_pair = $free_port; # This is a free port.
$free_port++; $free_ports .= $check_port.",";
$port_count++;
$check_port++;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
tcp_pair => $tcp_pair, free_ports => $free_ports,
free_port => $free_port, port_count => $port_count,
}}); }});
}
elsif ($tcp_pair !~ /,/) if ($port_count >= $neeed_ports)
{ {
$tcp_pair .= ",".$free_port;
$looking = 0; $looking = 0;
$free_ports =~ s/,$//;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
tcp_pair => $tcp_pair,
looking => $looking, looking => $looking,
free_ports => $free_ports,
}}); }});
} }
} }
else
{
$looking = 0;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { looking => $looking }});
}
}
}
if ($dr_tcp_ports)
{
$free_port = $tcp_pair;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { free_port => $free_port }});
} }
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
free_minor => $free_minor, free_minor => $free_minor,
free_port => $free_port, free_ports => $free_ports,
}}); }});
return($free_minor, $free_port); return($free_minor, $free_ports);
} }

@ -101,6 +101,7 @@ Requires: screen
Requires: smartmontools Requires: smartmontools
Requires: syslinux Requires: syslinux
Requires: tar Requires: tar
Requires: tcpdump
Requires: tmux Requires: tmux
Requires: unzip Requires: unzip
Requires: usbutils Requires: usbutils

@ -14,7 +14,8 @@
# TODO: # TODO:
# - Create a background script that is invoked when we see a resync is running that loops every few seconds # - Create a background script that is invoked when we see a resync is running that loops every few seconds
# and updates the progress in the database, exiting when the last resync is complete. # and updates the progress in the database, exiting when the last resync is complete.
# # - If the DRBD proxy file exists, see how long until it expires. May want to suppress alerts if no resources
# use it.
use strict; use strict;
use warnings; use warnings;
@ -90,6 +91,9 @@ if ($anvil->DRBD->gather_data({debug => 2}))
$problem = $anvil->Storage->manage_lvm_conf(); $problem = $anvil->Storage->manage_lvm_conf();
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { problem => $problem }}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { problem => $problem }});
# TODO: Remove this eventually.
check_schema($anvil);
read_last_scan($anvil); read_last_scan($anvil);
find_changes($anvil); find_changes($anvil);
@ -104,6 +108,41 @@ $anvil->ScanCore->agent_shutdown({agent => $THIS_FILE});
# Functions # # Functions #
############################################################################################################# #############################################################################################################
# In the early days, scan_drbd_peers -> scan_drbd_peer_tcp_port was type numeric. This wasn't compatible with
# drbd-proxy and had to be changed to type text to support csv port lists.
sub check_schema
{
my ($anvil) = @_;
my $query = "SELECT table_schema, data_type FROM information_schema.columns WHERE column_name = 'scan_drbd_peer_tcp_port';";
$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 $schema = $row->[0];
my $type = $row->[1];
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
schema => $schema,
type => $type,
}});
if ($type ne "text")
{
my $query = "ALTER TABLE ".$schema.".scan_drbd_peers ALTER COLUMN scan_drbd_peer_tcp_port TYPE text;";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 1, list => { query => $query }});
$anvil->Database->write({query => $query, source => $THIS_FILE, line => __LINE__});
}
}
return(0);
}
# This looks at the global-common.conf file and updates it, if needed. # This looks at the global-common.conf file and updates it, if needed.
sub check_config sub check_config
{ {
@ -506,6 +545,25 @@ sub process_peers
"s14:new_scan_drbd_peer_fencing" => $new_scan_drbd_peer_fencing, "s14:new_scan_drbd_peer_fencing" => $new_scan_drbd_peer_fencing,
}}); }});
# Is there a proxy config?
# my $new_scan_drbd_peer_inside_ip_address = "";
# my $new_scan_drbd_peer_inside_tcp_port = "";
# my $new_scan_drbd_peer_outside_ip_address = "";
# my $new_scan_drbd_peer_outside_tcp_port = "";
# if (exists $anvil->data->{new}{resource}{$resource}{proxy}{$peer_host_name})
# {
# $new_scan_drbd_peer_inside_ip_address = $anvil->data->{new}{resource}{$resource}{proxy}{$peer_host_name}{inside}{ip_address};
# $new_scan_drbd_peer_inside_tcp_port = $anvil->data->{new}{resource}{$resource}{proxy}{$peer_host_name}{inside}{ip_port};
# $new_scan_drbd_peer_outside_ip_address = $anvil->data->{new}{resource}{$resource}{proxy}{$peer_host_name}{outside}{ip_address};
# $new_scan_drbd_peer_outside_tcp_port = $anvil->data->{new}{resource}{$resource}{proxy}{$peer_host_name}{outside}{ip_port};
# $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
# "s1:new_scan_drbd_peer_inside_ip_address" => $new_scan_drbd_peer_inside_ip_address,
# "s2:new_scan_drbd_peer_inside_tcp_port" => $new_scan_drbd_peer_inside_tcp_port,
# "s3:new_scan_drbd_peer_outside_ip_address" => $new_scan_drbd_peer_outside_ip_address,
# "s4:new_scan_drbd_peer_outside_tcp_port" => $new_scan_drbd_peer_outside_tcp_port,
# }});
# }
if (exists $anvil->data->{old}{resource_to_uuid}{$resource}{volume}{$volume_number}{peer}{$peer_host_name}) if (exists $anvil->data->{old}{resource_to_uuid}{$resource}{volume}{$volume_number}{peer}{$peer_host_name})
{ {
# Look for changes # Look for changes

@ -196,7 +196,7 @@ CREATE TABLE scan_drbd_peers (
scan_drbd_peer_replication_speed numeric not null, -- This is how many bytes per second are being copied. Set to '0' when not synchronizing. scan_drbd_peer_replication_speed numeric not null, -- This is how many bytes per second are being copied. Set to '0' when not synchronizing.
scan_drbd_peer_estimated_time_to_sync numeric not null, -- This is the number of second that is *estimated* remaining in the resync. Set to '0' when both sides are UpToDate. scan_drbd_peer_estimated_time_to_sync numeric not null, -- This is the number of second that is *estimated* remaining in the resync. Set to '0' when both sides are UpToDate.
scan_drbd_peer_ip_address text not null, -- The (SN) IP address used for this peer. scan_drbd_peer_ip_address text not null, -- The (SN) IP address used for this peer.
scan_drbd_peer_tcp_port numeric not null, -- This is the port number used for this peer. scan_drbd_peer_tcp_port text not null, -- This is the port number used for this peer. It can be a CSV for drbd-proxy connections, hence being type text
scan_drbd_peer_protocol text not null, -- This is 'A' for async peers (to DR, usually) or 'C' to sync peers (node peer and sometimes DR) scan_drbd_peer_protocol text not null, -- This is 'A' for async peers (to DR, usually) or 'C' to sync peers (node peer and sometimes DR)
scan_drbd_peer_fencing text not null, -- Set to 'resource-and-stonith' for node peers and 'dont-care' for DR hosts. scan_drbd_peer_fencing text not null, -- Set to 'resource-and-stonith' for node peers and 'dont-care' for DR hosts.
modified_date timestamp with time zone not null, modified_date timestamp with time zone not null,
@ -221,7 +221,7 @@ CREATE TABLE history.scan_drbd_peers (
scan_drbd_peer_replication_speed numeric, scan_drbd_peer_replication_speed numeric,
scan_drbd_peer_estimated_time_to_sync numeric, scan_drbd_peer_estimated_time_to_sync numeric,
scan_drbd_peer_ip_address text, scan_drbd_peer_ip_address text,
scan_drbd_peer_tcp_port numeric, scan_drbd_peer_tcp_port text,
scan_drbd_peer_protocol text, scan_drbd_peer_protocol text,
scan_drbd_peer_fencing text, scan_drbd_peer_fencing text,
modified_date timestamp with time zone not null modified_date timestamp with time zone not null

@ -526,6 +526,10 @@ The definition data passed in was:
#!variable!output!# #!variable!output!#
====</key> ====</key>
<key name="error_0371">Can not (dis)connect the server: [#!variable!server!#] as the resource config file: [#!variable!config_file!#] doesn't exist. Do you need to '--protect' it?</key> <key name="error_0371">Can not (dis)connect the server: [#!variable!server!#] as the resource config file: [#!variable!config_file!#] doesn't exist. Do you need to '--protect' it?</key>
<key name="error_0372">We're set to migrate servers (--stop-servers not used) but both nodes are not in the cluster, so migrations would fail. Aborting.</key>
<key name="error_0373">Long-throw requires a license, and the license file is not installed, and '--license-file /path/to/drbd-proxy.license' was not passed.</key>
<key name="error_0374">The long-throw license file: [#!variable!file!#] was not found, so unable to install it.</key>
<key name="error_0375">There was a problem with the "Long-throw" lincense file. This will prevent Long-Throw DR from working. Details of the error will be recorded in the log file.</key>
<!-- Files templates --> <!-- Files templates -->
<!-- NOTE: Translating these files requires an understanding of which lines are translatable --> <!-- NOTE: Translating these files requires an understanding of which lines are translatable -->
@ -802,9 +806,38 @@ sys::manage::firewall = 1
]]></key> ]]></key>
<key name="file_0006"><![CDATA[# Resource for #!variable!server!# <key name="file_0006"><![CDATA[# Resource for #!variable!server!#
resource #!variable!server!# { resource #!variable!server!# {
#!variable!proxy!#
#!variable!hosts!# #!variable!hosts!#
#!variable!connections!# #!variable!connections!#
} }
]]></key>
<key name="file_0007"><![CDATA[
connection {
host #!variable!host1_short_name!# address 127.0.0.1:#!variable!tcp_port!# via proxy on #!variable!host1_short_name!# {
inside 127.0.0.1:#!variable!inside_tcp_port!#;
outside #!variable!host1_ip!#:#!variable!outside_tcp_port!#;
}
host #!variable!host2_short_name!# address 127.0.0.1:#!variable!tcp_port!# via proxy on #!variable!host2_short_name!# {
inside 127.0.0.1:#!variable!inside_tcp_port!#;
outside #!variable!host2_ip!#:#!variable!outside_tcp_port!#;
}
disk {
# The variable bit rate caps at 100 MiB/sec, setting this changes the maximum
# variable rate.
c-max-rate #!variable!c-rate-maximum!#M;
}
net {
protocol #!variable!protocol!#;
fencing #!variable!fencing!#;
}
}
]]></key>
<key name="file_0008"><![CDATA[# Resource for #!variable!server!#
proxy {
# You need to allocate >= 16MB per proxy connection to DRBD proxy for it to bring up a connection
memlimit #!variable!memlimiin!#M;
}
]]></key> ]]></key>
<!-- Table headers --> <!-- Table headers -->
@ -1379,6 +1412,7 @@ Note: This is a permanent action! If you protect this server again later, a full
<key name="job_0418">Re-parsing the replicated storage configuration.</key> <key name="job_0418">Re-parsing the replicated storage configuration.</key>
<key name="job_0419">The server: [#!variable!server!#] was found to be running outside the cluster. Asking it to shut down now.</key> <key name="job_0419">The server: [#!variable!server!#] was found to be running outside the cluster. Asking it to shut down now.</key>
<key name="job_0428">The server: [#!variable!server!#] is still running two minutes after asking it to stop. It might have woken up on the first press and ignored the shutdown request (Hi Windows). Pressing the poewr button again.</key> <key name="job_0428">The server: [#!variable!server!#] is still running two minutes after asking it to stop. It might have woken up on the first press and ignored the shutdown request (Hi Windows). Pressing the poewr button again.</key>
<key name="job_0429">Copying the Long-throw (drbd proxy) license file: [#!variable!file!#] into place.</key>
<!-- Log entries --> <!-- Log entries -->
<key name="log_0001">Starting: [#!variable!program!#].</key> <key name="log_0001">Starting: [#!variable!program!#].</key>
@ -1511,7 +1545,7 @@ The database connection error was:
- Record Locator: [#!variable!record_locator!#] - Record Locator: [#!variable!record_locator!#]
- Timestamp: .... [#!variable!modified_date!#] - Timestamp: .... [#!variable!modified_date!#]
</key> </key>
<key name="log_0099">[ Warning ] - There is no #!string!brand_0002!# database user set for the local machine. Please check: [#!data!path::config::anvil.conf!#]'s DB entry: [#!variable!uuid!#]. Using 'admin'.</key> <key name="log_0099">[ Warning ] - There is no #!string!brand_0002!# database user set for the local machine. Please check: [#!data!path::configs::anvil.conf!#]'s DB entry: [#!variable!uuid!#]. Using 'admin'.</key>
<key name="log_0100">Database user: [#!variable!user!#] password has been set/updated.</key> <key name="log_0100">Database user: [#!variable!user!#] password has been set/updated.</key>
<key name="log_0101">Failed to connect to: [#!variable!target!#:#!variable!port!#], sleeping for a second and then trying again.</key> <key name="log_0101">Failed to connect to: [#!variable!target!#:#!variable!port!#], sleeping for a second and then trying again.</key>
<key name="log_0102">I am not recording the alert with message_key: [#!variable!message_key!#] to the database because its log level was lower than any recipients.</key> <key name="log_0102">I am not recording the alert with message_key: [#!variable!message_key!#] to the database because its log level was lower than any recipients.</key>
@ -2208,6 +2242,10 @@ The file: [#!variable!file!#] needs to be updated. The difference is:
<key name="log_0725">Found the missing file: [#!variable!file!#] in the directory: [#!variable!directory!#]. Updating the database now.</key> <key name="log_0725">Found the missing file: [#!variable!file!#] in the directory: [#!variable!directory!#]. Updating the database now.</key>
<key name="log_0726">Deleting the hash key: [#!variable!hash_key!#].</key> <key name="log_0726">Deleting the hash key: [#!variable!hash_key!#].</key>
<key name="log_0727">[ Note ] - The server: [#!variable!server!#] is not yet off, but we've been told not to wait for it to stop.</key> <key name="log_0727">[ Note ] - The server: [#!variable!server!#] is not yet off, but we've been told not to wait for it to stop.</key>
<key name="log_0728">The DRBD Proxy license file: [#!data!path::configs::drbd-proxy.license!#] doesn't exist.</key>
<key name="log_0729">The DRBD Proxy license file has expired.</key>
<key name="log_0730">None of the MAC sddresses in the The DRBD Proxy license file match any of the MAC addresses on this system.</key>
<key name="log_0731">The DRBD Proxy license file: [#!data!path::configs::drbd-proxy.license!#] is missing expected data or is malformed.</key>
<!-- Messages for users (less technical than log entries), though sometimes used for logs, too. --> <!-- Messages for users (less technical than log entries), though sometimes used for logs, too. -->
<key name="message_0001">The host name: [#!variable!target!#] does not resolve to an IP address.</key> <key name="message_0001">The host name: [#!variable!target!#] does not resolve to an IP address.</key>

@ -32,7 +32,7 @@ $| = 1;
my $anvil = Anvil::Tools->new(); my $anvil = Anvil::Tools->new();
$anvil->Get->switches({list => ["connect", "disconnect", "job-uuid", "protect", "protocol", "remove", "server", "update", "Yes"], man => $THIS_FILE}); $anvil->Get->switches({list => ["connect", "disconnect", "job-uuid", "license-file", "protect", "protocol", "remove", "server", "update", "Yes"], man => $THIS_FILE});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => $anvil->data->{switches}}); $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 }}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0115", variables => { program => $THIS_FILE }});
@ -172,13 +172,13 @@ sub sanity_check
{ {
if (not $anvil->data->{switches}{protocol}) if (not $anvil->data->{switches}{protocol})
{ {
$anvil->data->{switches}{protocol} = "async"; $anvil->data->{switches}{protocol} = "short-throw";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
'switches::protocol' => $anvil->data->{switches}{protocol}, 'switches::protocol' => $anvil->data->{switches}{protocol},
}}); }});
} }
elsif (($anvil->data->{switches}{protocol} ne "sync") && elsif (($anvil->data->{switches}{protocol} ne "sync") &&
($anvil->data->{switches}{protocol} ne "async") && ($anvil->data->{switches}{protocol} ne "short-throw") &&
($anvil->data->{switches}{protocol} ne "long-throw")) ($anvil->data->{switches}{protocol} ne "long-throw"))
{ {
# The protocol is invalid. Please use '--help' for more information. # The protocol is invalid. Please use '--help' for more information.
@ -190,6 +190,61 @@ sub sanity_check
}); });
$anvil->nice_exit({exit_code => 1}); $anvil->nice_exit({exit_code => 1});
} }
if ($anvil->data->{switches}{protocol} eq "long-throw"))
{
# If there isn't a license file, make sure it's being provided.
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
'path::config::drbd-proxy.license' => $anvil->data->{path}{configs}{'drbd-proxy.license'},
}});
if (not -e $anvil->data->{path}{configs}{'drbd-proxy.license'})
{
if (not $anvil->data->{switches}{'license-file'})
{
# Proxy wouldn't work.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "error_0373"});
$anvil->Job->update_progress({
progress => 100,
message => "error_0373",
job_status => "failed",
});
$anvil->nice_exit({exit_code => 1});
}
elsif (not -f $anvil->data->{switches}{'license-file'})
{
# It was passed, but doesn't point to the file.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "error_0374", variables => { file => $anvil->data->{switches}{'license-file'} }});
$anvil->Job->update_progress({
progress => 100,
message => "error_0374,!!file!".$anvil->data->{switches}{'license-file'}."!!",
job_status => "failed",
});
$anvil->nice_exit({exit_code => 1});
}
# Still here? Copy the license file.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "job_0429", variables => { file => $anvil->data->{switches}{'license-file'} }});
my $failed = $anvil->Storage->copy_file({
source_file => $anvil->data->{switches}{'license-file'},
target_file => $anvil->data->{path}{configs}{'drbd-proxy.license'},
});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { failed => $failed }});
}
# Lastly, read in the license file to make sure it _looks_ ok.
my $problem = $anvil->DRBD->check_proxy_license({debug => 2});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { problem => $problem }});
if ($problem)
{
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "error_0375"});
$anvil->Job->update_progress({
progress => 100,
message => "error_0375",
job_status => "failed",
});
$anvil->nice_exit({exit_code => 1});
}
}
} }
} }
@ -261,8 +316,8 @@ sub sanity_check
$anvil->nice_exit({exit_code => 1}); $anvil->nice_exit({exit_code => 1});
} }
### TODO: We can queue a job to update the peer later, there's no real need, in the long run, for the ### TODO: We can queue a job to update the peer later, there's no real need, in the long run,
### peer to be online. ### for the peer to be online.
# If we're protecting or removing a server from DR, the peer needs to be up. # If we're protecting or removing a server from DR, the peer needs to be up.
if ((($anvil->data->{switches}{protect}) or if ((($anvil->data->{switches}{protect}) or
($anvil->data->{switches}{remove}) or ($anvil->data->{switches}{remove}) or
@ -271,8 +326,8 @@ sub sanity_check
{ {
if ($anvil->data->{switches}{protect}) if ($anvil->data->{switches}{protect})
{ {
# We can't setup a server to be protected unless both nodes are up, and the peer # We can't setup a server to be protected unless both nodes are up, and the
# isn't at this time. # peer isn't at this time.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "error_0338"}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "error_0338"});
$anvil->Job->update_progress({ $anvil->Job->update_progress({
progress => 0, progress => 0,
@ -282,8 +337,8 @@ sub sanity_check
} }
else else
{ {
# We can't remove a server from DR unless both nodes are up, and the peer isn't at # We can't remove a server from DR unless both nodes are up, and the peer
# this time. # isn't at this time.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "error_0339"}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "error_0339"});
$anvil->Job->update_progress({ $anvil->Job->update_progress({
progress => 0, progress => 0,
@ -1957,7 +2012,11 @@ sub process_protect
# Have we already configured the DR? If so, what ports are already allocated. # Have we already configured the DR? If so, what ports are already allocated.
my $node1_to_dr_port = ""; my $node1_to_dr_port = "";
my $node1_to_dr_port_inside = "";
my $node1_to_dr_port_outside = "";
my $node2_to_dr_port = ""; my $node2_to_dr_port = "";
my $node2_to_dr_port_inside = "";
my $node2_to_dr_port_outside = "";
foreach my $host1_name (sort {$a cmp $b} keys %{$anvil->data->{new}{resource}{$server_name}{host1_to_host2}}) foreach my $host1_name (sort {$a cmp $b} keys %{$anvil->data->{new}{resource}{$server_name}{host1_to_host2}})
{ {
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { host1_name => $host1_name }}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { host1_name => $host1_name }});
@ -1965,45 +2024,98 @@ sub process_protect
{ {
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { host2_name => $host2_name }}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { host2_name => $host2_name }});
next if (($host1_name ne $dr1_short_host_name) && ($host2_name ne $dr1_short_host_name)); next if (($host1_name ne $dr1_short_host_name) && ($host2_name ne $dr1_short_host_name));
if (($host1_name eq $node1_short_host_name) or ($host2_name eq $node1_short_host_name)) if (($host1_name eq $node1_short_host_name) or ($host2_name eq $node1_short_host_name))
{ {
$node1_to_dr_port = $anvil->data->{new}{resource}{$server_name}{host1_to_host2}{$host1_name}{$host2_name}{host1_tcp_port}; $node1_to_dr_port = $anvil->data->{new}{resource}{$server_name}{host1_to_host2}{$host1_name}{$host2_name}{host1_tcp_port};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { node1_to_dr_port => $node1_to_dr_port }}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { node1_to_dr_port => $node1_to_dr_port }});
if (exists $anvil->data->{new}{resource}{$resource}{host1_to_host2}{$host1_name}{$host2_name}{host2_inside_tcp_port})
{
$node1_to_dr_port_inside = $anvil->data->{new}{resource}{$resource}{host1_to_host2}{$host1_name}{$host2_name}{host2_inside_tcp_port};
$node1_to_dr_port_outside = $anvil->data->{new}{resource}{$resource}{host1_to_host2}{$host1_name}{$host2_name}{host2_outside_tcp_port};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
node1_to_dr_port_inside => $node1_to_dr_port_inside,
node1_to_dr_port_outside => $node1_to_dr_port_outside,
}});
}
} }
else else
{ {
$node2_to_dr_port = $anvil->data->{new}{resource}{$server_name}{host1_to_host2}{$host1_name}{$host2_name}{host1_tcp_port}; $node2_to_dr_port = $anvil->data->{new}{resource}{$server_name}{host1_to_host2}{$host1_name}{$host2_name}{host1_tcp_port};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { node2_to_dr_port => $node2_to_dr_port }}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { node2_to_dr_port => $node2_to_dr_port }});
if (exists $anvil->data->{new}{resource}{$resource}{host1_to_host2}{$host1_name}{$host2_name}{host2_inside_tcp_port})
{
$node2_to_dr_port_inside = $anvil->data->{new}{resource}{$resource}{host1_to_host2}{$host1_name}{$host2_name}{host2_inside_tcp_port};
$node2_to_dr_port_outside = $anvil->data->{new}{resource}{$resource}{host1_to_host2}{$host1_name}{$host2_name}{host2_outside_tcp_port};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
node2_to_dr_port_inside => $node2_to_dr_port_inside,
node2_to_dr_port_outside => $node2_to_dr_port_outside,
}});
}
} }
} }
} }
# Get net next pair of TCP ports, if needed. # Get net next pair of TCP ports, if needed.
my $dr_tcp_ports = 1;
my $long_throw_ports = 0;
if ($anvil->data->{switches}{protocol} eq "long-throw")
{
$dr_tcp_ports = 0;
$long_throw_ports = 1;
}
my (undef, $tcp_ports) = $anvil->DRBD->get_next_resource({ my (undef, $tcp_ports) = $anvil->DRBD->get_next_resource({
debug => 2, debug => 2,
dr_tcp_ports => 1, dr_tcp_ports => $dr_tcp_ports,
long_throw_ports => $long_throw_ports,
}); });
my ($first_port, $second_port) = split/,/, $tcp_ports; my @free_ports = split/,/, $tcp_ports;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { my $i = 0;
"s01:first_port" => $first_port,
"s02:second_port" => $second_port,
}});
if ($node1_to_dr_port eq "") if ($node1_to_dr_port eq "")
{ {
$node1_to_dr_port = $first_port; $node1_to_dr_port = $free_ports[$i++];
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { node1_to_dr_port => $node1_to_dr_port }}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { node1_to_dr_port => $node1_to_dr_port }});
} }
if (($long_throw_ports) && (not $node1_to_dr_port_inside))
{
$node1_to_dr_port_inside = $free_ports[$i++];
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { node1_to_dr_port_inside => $node1_to_dr_port_inside }});
}
if (($long_throw_ports) && (not $node1_to_dr_port_outside))
{
$node1_to_dr_port_outside = $free_ports[$i++];
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { node1_to_dr_port_outside => $node1_to_dr_port_outside }});
}
if ($node2_to_dr_port eq "") if ($node2_to_dr_port eq "")
{ {
$node2_to_dr_port = $second_port; $node2_to_dr_port = $free_ports[$i];
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { node2_to_dr_port => $node2_to_dr_port }}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { node2_to_dr_port => $node2_to_dr_port }});
} }
if (($long_throw_ports) && (not $node2_to_dr_port_inside))
{
$node2_to_dr_port_inside = $free_ports[$i++];
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { node2_to_dr_port_inside => $node2_to_dr_port_inside }});
}
if (($long_throw_ports) && (not $node2_to_dr_port_outside))
{
$node2_to_dr_port_outside = $free_ports[$i++];
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { node2_to_dr_port_outside => $node2_to_dr_port_outside }});
}
# Show what we're doing # Show what we're doing
my $variables = { my $variables = {
protocol => $anvil->data->{switches}{protocol}, protocol => $anvil->data->{switches}{protocol},
node1_to_dr_port => $node1_to_dr_port, node1_to_dr_port => $node1_to_dr_port,
node1_to_dr_port_inside => $node1_to_dr_port_inside,
node1_to_dr_port_outside => $node1_to_dr_port_outside,
node2_to_dr_port => $node2_to_dr_port, node2_to_dr_port => $node2_to_dr_port,
node2_to_dr_port_inside => $node2_to_dr_port_inside,
node2_to_dr_port_outside => $node2_to_dr_port_outside,
config_file => $config_file, config_file => $config_file,
}; };
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "job_0361", variables => $variables}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "job_0361", variables => $variables});
@ -2282,21 +2394,44 @@ sub process_protect
}}); }});
# Choose the DR protocol # Choose the DR protocol
my $use_drbd_proxy = 0;
my $dr_protocol = "A"; my $dr_protocol = "A";
if ($anvil->data->{switches}{protocol} eq "sync") if ($anvil->data->{switches}{protocol} eq "sync")
{ {
$dr_protocol = "C"; $dr_protocol = "C";
} }
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { dr_protocol => $dr_protocol }}); elsif ($anvil->data->{switches}{protocol} eq "long-throw")
{
$use_drbd_proxy = 1;
}
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
dr_protocol => $dr_protocol,
use_drbd_proxy => $use_drbd_proxy,
}});
# Node 1 to Node 2 first # Node 1 to Node 2 first
my $proxy = "";
my $max_c_rate = 500;
my $file_key = "file_0005";
if ($use_drbd_proxy)
{
$proxy = $anvil->Words->string({key => "file_0008", variables => { memlimit => 256 }});
$max_c_rate = 100;
$file_key = "file_0007";
}
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
proxy => $proxy,
max_c_rate => $max_c_rate,
file_key => $file_key,
}});
my $connections = $anvil->Words->string({key => "file_0005", variables => { my $connections = $anvil->Words->string({key => "file_0005", variables => {
host1_short_name => $node1_short_host_name, host1_short_name => $node1_short_host_name,
host1_ip => $node1_sn_ip, host1_ip => $node1_sn_ip,
host2_short_name => $node2_short_host_name, host2_short_name => $node2_short_host_name,
host2_ip => $node2_sn_ip, host2_ip => $node2_sn_ip,
tcp_port => $nodes_tcp_port, tcp_port => $nodes_tcp_port,
'c-rate-maximum' => 500, 'c-rate-maximum' => $max_c_rate,
protocol => "C", protocol => "C",
fencing => "resource-and-stonith" fencing => "resource-and-stonith"
}}); }});
@ -2308,7 +2443,9 @@ sub process_protect
host2_short_name => $dr1_short_host_name, host2_short_name => $dr1_short_host_name,
host2_ip => $dr1_ip, host2_ip => $dr1_ip,
tcp_port => $node1_to_dr_port, tcp_port => $node1_to_dr_port,
'c-rate-maximum' => 500, inside_tcp_port => $node1_to_dr_port_inside,
outside_tcp_port => $node1_to_dr_port_outside,
'c-rate-maximum' => $max_c_rate,
protocol => $dr_protocol, protocol => $dr_protocol,
fencing => "dont-care" fencing => "dont-care"
}}); }});
@ -2320,13 +2457,17 @@ sub process_protect
host2_short_name => $dr1_short_host_name, host2_short_name => $dr1_short_host_name,
host2_ip => $dr1_ip, host2_ip => $dr1_ip,
tcp_port => $node2_to_dr_port, tcp_port => $node2_to_dr_port,
'c-rate-maximum' => 500, inside_tcp_port => $node2_to_dr_port_inside,
outside_tcp_port => $node2_to_dr_port_outside,
'c-rate-maximum' => $max_c_rate,
protocol => $dr_protocol, protocol => $dr_protocol,
fencing => "dont-care" fencing => "dont-care"
}}); }});
my $new_resource_config = $anvil->Words->string({key => "file_0006", variables => { my $new_resource_config = $anvil->Words->string({key => "file_0006", variables => {
server => $server_name, server => $server_name,
proxy => $proxy,
hosts => $hosts, hosts => $hosts,
connections => $connections, connections => $connections,
}}); }});

@ -759,6 +759,8 @@ sub startup_resource
task => "up", task => "up",
}); });
# If both sides are Inconsistent, for node 1 to primary
my $waiting = 1; my $waiting = 1;
while($waiting) while($waiting)
{ {

@ -8,6 +8,9 @@
# #
# TODO: # TODO:
# #
# BUG:
# - --poweroff when the peer is offline tries to migrate anyway.
# -
use strict; use strict;
use warnings; use warnings;
@ -265,19 +268,6 @@ sub process_servers
{ {
my ($anvil) = @_; my ($anvil) = @_;
if ($anvil->data->{switches}{'stop-servers'})
{
# Tell the user we're about to shut down servers.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "job_0320"});
$anvil->Job->update_progress({progress => 10, message => "job_0320"});
}
else
{
# Tell the user we're about to migrate servers.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "job_0321"});
$anvil->Job->update_progress({progress => 10, message => "job_0321"});
}
# Use virsh to check for servers, in case pacemaker lies to us. # Use virsh to check for servers, in case pacemaker lies to us.
$anvil->Server->find(); $anvil->Server->find();
my $progress = 10; my $progress = 10;
@ -291,6 +281,51 @@ sub process_servers
's1:server_count' => $server_count, 's1:server_count' => $server_count,
's2:progress_steps' => $progress_steps, 's2:progress_steps' => $progress_steps,
}}); }});
# If we have one or more local servers, we need to know if both of us are in the cluster. If we're
# not, or the peer isn't, we can't migrate.
my $can_migrate = 0;
if ($server_count)
{
my $problem = $anvil->Cluster->parse_cib({debug => 2});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
's1:problem' => $problem,
's2:cib::parsed::local::ready' => $anvil->data->{cib}{parsed}{'local'}{ready},
's3:cib::parsed::peer::ready' => $anvil->data->{cib}{parsed}{peer}{ready},
}});
if ($problem)
{
$can_migrate = 0;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { can_migrate => $can_migrate }});
}
elsif ((not $anvil->data->{cib}{parsed}{'local'}{ready}) or (not $anvil->data->{cib}{parsed}{peer}{ready}))
{
$can_migrate = 0;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { can_migrate => $can_migrate }});
}
if ((not $anvil->data->{switches}{'stop-servers'}) && (not $can_migrate))
{
# Abort.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, priority => "err", key => "error_0372"});
$anvil->Job->update_progress({progress => 100, message => "error_0372"});
$anvil->nice_exit({exit_code => 1});
}
}
if ($anvil->data->{switches}{'stop-servers'})
{
# Tell the user we're about to shut down servers.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "job_0320"});
$anvil->Job->update_progress({progress => 10, message => "job_0320"});
}
else
{
# Tell the user we're about to migrate servers.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "job_0321"});
$anvil->Job->update_progress({progress => 10, message => "job_0321"});
}
while ($waiting) while ($waiting)
{ {
# Is the cluster up? # Is the cluster up?

Loading…
Cancel
Save