diff --git a/Anvil/Tools.pm b/Anvil/Tools.pm
index 168fccb6..97a78bcc 100644
--- a/Anvil/Tools.pm
+++ b/Anvil/Tools.pm
@@ -1108,6 +1108,7 @@ sub _set_paths
'anvil-file-details' => "/usr/sbin/anvil-file-details",
'anvil-join-anvil' => "/usr/sbin/anvil-join-anvil",
'anvil-maintenance-mode' => "/usr/sbin/anvil-maintenance-mode",
+ 'anvil-manage-dr' => "/usr/sbin/anvil-manage-dr",
'anvil-manage-firewall' => "/usr/sbin/anvil-manage-firewall",
'anvil-manage-keys' => "/usr/sbin/anvil-manage-keys",
'anvil-manage-power' => "/usr/sbin/anvil-manage-power",
diff --git a/Anvil/Tools/Database.pm b/Anvil/Tools/Database.pm
index ed0b6b23..c7653153 100644
--- a/Anvil/Tools/Database.pm
+++ b/Anvil/Tools/Database.pm
@@ -4564,7 +4564,7 @@ SELECT
FROM
scan_lvm_vgs
WHERE
- scan_lvm_vg_internal_uuid = ".$anvil->Database->quote($storage_group_member_vg_uuid).";
+ scan_lvm_vg_internal_uuid = ".$anvil->Database->quote($storage_group_member_vg_uuid)."
;";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { query => $query }});
@@ -4585,6 +4585,221 @@ WHERE
}});
}
}
+
+ # Also load the Storage group extended data.
+ $anvil->Storage->get_storage_group_details({
+ debug => $debug,
+ storage_group_uuid => $storage_group_uuid,
+ });
+ }
+
+ # If the Anvil! members have changed, we'll need to update the storage groups. This checks for that.
+ $anvil->Database->get_anvils({debug => $debug});
+ foreach my $anvil_uuid (keys %{$anvil->data->{storage_groups}{anvil_uuid}})
+ {
+ my $anvil_name = $anvil->data->{anvils}{anvil_uuid}{$anvil_uuid}{anvil_name};
+ 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 $dr1_host_uuid = $anvil->data->{anvils}{anvil_uuid}{$anvil_uuid}{anvil_dr1_host_uuid};
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
+ anvil_name => $anvil_name,
+ node1_host_uuid => $node1_host_uuid,
+ node2_host_uuid => $node2_host_uuid,
+ dr1_host_uuid => $dr1_host_uuid,
+ }});
+ foreach my $storage_group_uuid (keys %{$anvil->data->{storage_groups}{anvil_uuid}{$anvil_uuid}{storage_group_uuid}})
+ {
+ my $group_name = $anvil->data->{storage_groups}{anvil_uuid}{$anvil_uuid}{storage_group_uuid}{$storage_group_uuid}{group_name};
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
+ storage_group_uuid => $storage_group_uuid,
+ group_name => $group_name,
+ }});
+
+ my $size_to_match = 0;
+ my $node1_seen = 0;
+ my $node2_seen = 0;
+ my $dr1_seen = $dr1_host_uuid ? 0 : 1; # Only set to '0' if DR exists.
+ foreach my $this_host_uuid (keys %{$anvil->data->{storage_groups}{anvil_uuid}{$anvil_uuid}{storage_group_uuid}{$storage_group_uuid}{host_uuid}})
+ {
+ my $storage_group_member_uuid = $anvil->data->{storage_groups}{anvil_uuid}{$anvil_uuid}{storage_group_uuid}{$storage_group_uuid}{host_uuid}{$this_host_uuid}{storage_group_member_uuid};
+ my $internal_vg_uuid = $anvil->data->{storage_groups}{anvil_uuid}{$anvil_uuid}{storage_group_uuid}{$storage_group_uuid}{host_uuid}{$this_host_uuid}{vg_internal_uuid};
+ my $vg_size = $anvil->data->{storage_groups}{anvil_uuid}{$anvil_uuid}{storage_group_uuid}{$storage_group_uuid}{host_uuid}{$this_host_uuid}{vg_size};
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
+ this_host_uuid => $this_host_uuid,
+ storage_group_member_uuid => $storage_group_member_uuid,
+ internal_vg_uuid => $internal_vg_uuid,
+ vg_size => $anvil->Convert->add_commas({number => $vg_size})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $vg_size}).")",
+ }});
+
+ if ($vg_size > $size_to_match)
+ {
+ $size_to_match = $vg_size;
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
+ size_to_match => $anvil->Convert->add_commas({number => $size_to_match})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $size_to_match}).")",
+ }});
+ }
+
+ if ($this_host_uuid eq $node1_host_uuid)
+ {
+ $node1_seen = 1;
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { node1_seen => $node1_seen }});
+ }
+ elsif ($this_host_uuid eq $node2_host_uuid)
+ {
+ $node2_seen = 1;
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { node2_seen => $node2_seen }});
+ }
+ elsif (($dr1_host_uuid) && ($this_host_uuid eq $dr1_host_uuid))
+ {
+ $dr1_seen = 1;
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { dr1_seen => $dr1_seen }});
+ }
+ else
+ {
+ # This host doesn't belong in this group anymore. Delete it.
+ $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, priority => "alert", key => "warning_0130", variables => {
+ storage_group_name => $group_name,
+ host_name => $anvil->Get->host_name_from_uuid({host_uuid => $this_host_uuid}),
+ anvil_name => $anvil_name,
+ }});
+
+ my $query = "DELETE FROM storage_group_members WHERE storage_group_member_uuid = ".$anvil->Database->quote($storage_group_member_uuid).";";
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 1, list => { query => $query }});
+ $anvil->Database->write({query => $query, source => $THIS_FILE, line => __LINE__});
+ }
+ }
+
+ if ((not $node1_seen) or
+ (not $node2_seen) or
+ (not $dr1_seen))
+ {
+ my $hosts = [$node1_host_uuid, $node2_host_uuid];
+ if ($dr1_host_uuid)
+ {
+ push @{$hosts}, $dr1_host_uuid;
+ }
+
+ my $reload = 0;
+ foreach my $this_host_uuid (@{$hosts})
+ {
+ # If we didn't see a host, look for a compatible VG to add.
+ my $minimum_size = $size_to_match - (2**30);
+ my $maximum_size = $size_to_match + (2**30);
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
+ minimum_size => $anvil->Convert->add_commas({number => $minimum_size})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $minimum_size}).")",
+ maximum_size => $anvil->Convert->add_commas({number => $maximum_size})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $maximum_size}).")",
+ }});
+
+ my $smallest_difference = (2**30);
+ my $closest_internal_uuid = "";
+ my $closest_scan_lvm_vg_uuid = "";
+ my $quoted_minimum_size = $anvil->Database->quote($minimum_size);
+ $quoted_minimum_size =~ s/^'(.*)'$/$1/;
+ my $quoted_maximum_size = $anvil->Database->quote($maximum_size);
+ $quoted_maximum_size =~ s/^'(.*)'$/$1/;
+ my $query = "
+SELECT
+ scan_lvm_vg_uuid,
+ scan_lvm_vg_internal_uuid,
+ scan_lvm_vg_size
+FROM
+ scan_lvm_vgs
+WHERE
+ scan_lvm_vg_size > ".$quoted_minimum_size."
+AND
+ scan_lvm_vg_size < ".$quoted_maximum_size."
+AND
+ scan_lvm_vg_host_uuid = ".$anvil->Database->quote($this_host_uuid)."
+ORDER BY
+ scan_lvm_vg_size ASC
+;";
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, 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 => $debug, list => {
+ results => $results,
+ count => $count,
+ }});
+ foreach my $row (@{$results})
+ {
+ my $scan_lvm_vg_uuid = $row->[0];
+ my $scan_lvm_vg_internal_uuid = $row->[1];
+ my $scan_lvm_vg_size = $row->[2];
+ my $difference = abs($scan_lvm_vg_size - $size_to_match);
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
+ scan_lvm_vg_uuid => $scan_lvm_vg_uuid,
+ scan_lvm_vg_internal_uuid => $scan_lvm_vg_internal_uuid,
+ scan_lvm_vg_size => $anvil->Convert->add_commas({number => $scan_lvm_vg_size})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $scan_lvm_vg_size}).")",
+ difference => $anvil->Convert->add_commas({number => $difference})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $difference}).")",
+ }});
+
+ # Is this Internal UUID already in a storage group?
+ my $query = "SELECT COUNT(*) FROM storage_group_members WHERE storage_group_member_vg_uuid = ".$anvil->Database->quote($scan_lvm_vg_internal_uuid).";";
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { query => $query }});
+
+ my $count = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__})->[0]->[0];
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { count => $count }});
+ if (not $count)
+ {
+ # This VG isn't in a storage group. Is this the closest in size yet?
+ if ($difference < $smallest_difference)
+ {
+ # Closest yet!
+ $smallest_difference = $difference;
+ $closest_internal_uuid = $scan_lvm_vg_internal_uuid;
+ $closest_scan_lvm_vg_uuid = $scan_lvm_vg_uuid;
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
+ smallest_difference => $anvil->Convert->add_commas({number => $smallest_difference})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $smallest_difference}).")",
+ closest_internal_uuid => $closest_internal_uuid,
+ closest_scan_lvm_vg_uuid => $closest_scan_lvm_vg_uuid,
+ }});
+ }
+ }
+ }
+
+ # Did we find a matching VG?
+ if ($closest_scan_lvm_vg_uuid)
+ {
+ # Yup, add it!
+ $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, priority => "alert", key => "log_0649", variables => {
+ anvil_name => $anvil_name,
+ storage_group => $group_name,
+ host_name => $anvil->Get->host_name_from_uuid({host_uuid => $this_host_uuid}),
+ vg_internal_uuid => $closest_scan_lvm_vg_uuid,
+ }});
+
+ my $storage_group_member_uuid = $anvil->Get->uuid();
+ my $query = "
+INSERT INTO
+ storage_group_members
+(
+ storage_group_member_uuid,
+ storage_group_member_storage_group_uuid,
+ storage_group_member_host_uuid,
+ storage_group_member_vg_uuid,
+ modified_date
+) VALUES (
+ ".$anvil->Database->quote($storage_group_member_uuid).",
+ ".$anvil->Database->quote($storage_group_uuid).",
+ ".$anvil->Database->quote($this_host_uuid).",
+ ".$anvil->Database->quote($closest_scan_lvm_vg_uuid).",
+ ".$anvil->Database->quote($anvil->Database->refresh_timestamp)."
+);";
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 1, list => { query => $query }});
+ $anvil->Database->write({query => $query, source => $THIS_FILE, line => __LINE__});
+
+ # Reload
+ $reload = 1;
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { reload => $reload }});
+ }
+ }
+ if ($reload)
+ {
+ $anvil->Database->get_storage_group_data({debug => $debug});
+ }
+ }
+ }
}
return(0);
diff --git a/Anvil/Tools/Storage.pm b/Anvil/Tools/Storage.pm
index d110e954..c9bb2444 100644
--- a/Anvil/Tools/Storage.pm
+++ b/Anvil/Tools/Storage.pm
@@ -27,6 +27,7 @@ my $THIS_FILE = "Storage.pm";
# get_size_of_block_device
# get_storage_group_details
# get_storage_group_from_path
+# get_vg_name
# make_directory
# manage_lvm_conf
# move_file
@@ -1793,7 +1794,7 @@ LIMIT 1
foreach my $this_host_name (sort {$a cmp $b} keys %{$anvil->data->{new}{resource}{$this_resource}{host}})
{
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { this_host_name => $this_host_name }});
- foreach my $this_volume (sort {$a cmp $b} keys %{$$anvil->data->{new}{resource}{$this_resource}{host}{$this_host_name}{volume}})
+ foreach my $this_volume (sort {$a cmp $b} keys %{$anvil->data->{new}{resource}{$this_resource}{host}{$this_host_name}{volume}})
{
my $this_minor = $anvil->data->{new}{resource}{$this_resource}{host}{$this_host_name}{volume}{$this_volume}{device_minor};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
@@ -1938,6 +1939,8 @@ This takes a C<< storage_group_uuid >> and loads information about members into
On success, C<< 0 >> is returned. On failure, C<< !!error!! >> is returned.
+B<< Note >>: This method is called by C<< Database->get_storage_group_data() >> so generally calling it direcly isn't needed.
+
Parameters;
=head3 storage_group_uuid (required)
@@ -2150,7 +2153,7 @@ sub get_storage_group_from_path
foreach my $this_host_name (sort {$a cmp $b} keys %{$anvil->data->{new}{resource}{$this_resource}{host}})
{
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { this_host_name => $this_host_name }});
- foreach my $this_volume (sort {$a cmp $b} keys %{$$anvil->data->{new}{resource}{$this_resource}{host}{$this_host_name}{volume}})
+ foreach my $this_volume (sort {$a cmp $b} keys %{$anvil->data->{new}{resource}{$this_resource}{host}{$this_host_name}{volume}})
{
my $this_minor = $anvil->data->{new}{resource}{$this_resource}{host}{$this_host_name}{volume}{$this_volume}{device_minor};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
@@ -2382,6 +2385,86 @@ LIMIT 1
}
+=head2 get_vg_name
+
+This method takes a Storage Group UUID and a host UUID, and returns the volume group name associated with those. If there is a problem, C<< !!error!! >> is returned.
+
+ my $vg_name = $anvil->Storage->get_vg_name({
+ host_uuid => $dr_host_uuid,
+ storage_group_uuid => $storage_group_uuid,
+ });
+
+Parameters;
+
+=head3 host_uuid (optional, default Get->host_uuid)
+
+This is the host's UUID that holds the VG name being searched for.
+
+=head3 storage_group_uuid (required)
+
+This is the Storage Group UUID being searched for.
+
+=cut
+sub get_vg_name
+{
+ my $self = shift;
+ my $parameter = shift;
+ my $anvil = $self->parent;
+ my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3;
+ my $test = defined $parameter->{test} ? $parameter->{test} : 0;
+ $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Storage->get_vg_name()" }});
+
+ my $host_uuid = defined $parameter->{host_uuid} ? $parameter->{host_uuid} : "";
+ my $storage_group_uuid = defined $parameter->{storage_group_uuid} ? $parameter->{storage_group_uuid} : "";
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
+ host_uuid => $host_uuid,
+ storage_group_uuid => $storage_group_uuid,
+ }});
+
+ if (not $host_uuid)
+ {
+ $host_uuid = $anvil->Get->host_uuid();
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { host_uuid => $host_uuid }});
+ }
+ if (not $storage_group_uuid)
+ {
+ $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Storage->get_vg_name()", parameter => "storage_group_uuid" }});
+ return('!!error!!');
+ }
+
+ my $query = "
+SELECT
+ b.scan_lvm_vg_name
+FROM
+ storage_group_members a,
+ scan_lvm_vgs b
+WHERE
+ a.storage_group_member_vg_uuid = b.scan_lvm_vg_internal_uuid
+AND
+ a.storage_group_member_storage_group_uuid = ".$anvil->Database->quote($storage_group_uuid)."
+AND
+ a.storage_group_member_host_uuid = ".$anvil->Database->quote($host_uuid)."
+;";
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, 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 => $debug, list => {
+ results => $results,
+ count => $count,
+ }});
+ if (not $count)
+ {
+ # Not found
+ return("");
+ }
+
+ my $scan_lvm_vg_name = $results->[0]->[0];
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { scan_lvm_vg_name => $scan_lvm_vg_name }});
+
+ return($scan_lvm_vg_name);
+}
+
+
=head2 make_directory
This creates a directory (and any parent directories).
diff --git a/share/words.xml b/share/words.xml
index 5e521616..f549d782 100644
--- a/share/words.xml
+++ b/share/words.xml
@@ -1148,6 +1148,8 @@ It should be provisioned in the next minute or two.
* Please enter the name of the server you want to manage
-=] Servers available to manage on the Anvil! [#!variable!anvil_name!#] [=-
-=] Managing the server: [#!variable!server_name!#] on the Anvil!: [#!variable!anvil_name!#]
+ Manage DR tasks for a given server
+ This job can protect, remove (unprotect), connect, disconnect or update (connect, sync, disconnect) a given server.
Starting: [#!variable!program!#].
@@ -1898,6 +1900,7 @@ The file: [#!variable!file!#] needs to be updated. The difference is:
#!variable!program!# is disabled in anvil.conf. and '--force' was not used. Exiting.
[ Note ] - The network interface: [#!variable!name!#] with 'network_interface_uuid': [#!variable!uuid!#] is a duplicate, removing it from the database(s).
[ Note ] - Managing /etc/hosts has been disabled.
+ [ Note ] - The Anvil!: [#!variable!anvil_name!#]'s storage group: [#!variable!storage_group!#] didn't have an entry for the host: [#!variable!host_name!#]. The volume group: [#!variable!vg_internal_uuid!#] is a close fit and not in another storage group, so adding it to this storage group now.
The host name: [#!variable!target!#] does not resolve to an IP address.
@@ -2256,6 +2259,7 @@ Are you sure that you want to delete the server: [#!variable!server_name!#]? [Ty
Finished [#!variable!operation!#] VNC pipe for server UUID [#!variable!server_uuid!#] from host UUID [#!variable!host_uuid!#].
Finished dropping VNC pipes table.
Finished managing VNC pipes; no operations happened because requirements not met.
+ Preparing to manage DR for a server.
Saved the mail server information successfully!
@@ -2916,6 +2920,7 @@ The error was:
We will sleep a bit and try again.
+ [ Warning ] - The storage group: [#!variable!storage_group_name!#] had the host: [#!variable!host_name!#] as a member. This host is not a member (anymore?) of the Anvil!: [#!variable!anvil_name!#]. Removing it from the storage group now.
diff --git a/tools/anvil-manage-dr b/tools/anvil-manage-dr
new file mode 100755
index 00000000..cb0577a1
--- /dev/null
+++ b/tools/anvil-manage-dr
@@ -0,0 +1,715 @@
+#!/usr/bin/perl
+#
+# This manages if a server is backed up to a DR host or not. When enabled, it can start or stop replication.
+#
+# NOTE: Unlike most jobs, this one will directly work on the peer node and the DR host using SSH connections.
+# This behaviour is likely to change later as it's not ideal.
+#
+# Exit codes;
+# 0 = Normal exit.
+# 1 = Any problem that causes an early exit.
+#
+
+use strict;
+use warnings;
+use Anvil::Tools;
+require POSIX;
+use Term::Cap;
+
+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();
+
+#
+$anvil->data->{switches}{'connect'} = ""; # connect an existing DR resource
+$anvil->data->{switches}{disconnect} = ""; # disconnect
+$anvil->data->{switches}{'job-uuid'} = ""; # Used later
+$anvil->data->{switches}{protect} = ""; # Set
+$anvil->data->{switches}{protocol} = ""; # "sync", "async" or "long-throw"
+$anvil->data->{switches}{remove} = ""; # Set
+$anvil->data->{switches}{server} = ""; # Name or UUID
+$anvil->data->{switches}{update} = ""; # connects, if needed, and disconnects once UpToDate
+$anvil->data->{switches}{Yes} = ""; # Set to avoid confirmation, not case sensitive
+$anvil->Get->switches;
+$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, secure => 0, key => "log_0115", variables => { program => $THIS_FILE }});
+$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
+ 'switches::connect' => $anvil->data->{switches}{'connect'},
+ 'switches::disconnect' => $anvil->data->{switches}{disconnect},
+ 'switches::job-uuid' => $anvil->data->{switches}{'job-uuid'},
+ 'switches::protect' => $anvil->data->{switches}{protect},
+ 'switches::protocol' => $anvil->data->{switches}{protocol},
+ 'switches::remove' => $anvil->data->{switches}{remove},
+ 'switches::server' => $anvil->data->{switches}{server},
+ 'switches::update' => $anvil->data->{switches}{update},
+ 'switches::Yes' => $anvil->data->{switches}{Yes},
+}});
+
+$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_0306"});
+ sleep 10;
+ $anvil->nice_exit({exit_code => 1});
+}
+
+# If we've got a job UUID, load the job details.
+if ($anvil->data->{switches}{'job-uuid'})
+{
+ load_job($anvil);
+}
+
+sanity_check($anvil);
+
+do_task($anvil);
+
+
+$anvil->nice_exit({exit_code => 0});
+
+
+#############################################################################################################
+# Functions #
+#############################################################################################################
+
+sub do_task
+{
+ my ($anvil) = @_;
+
+ # What task am I doing?
+ if ($anvil->data->{switches}{protect})
+ {
+
+ }
+
+ return(0);
+}
+
+sub sanity_check
+{
+ my ($anvil) = @_;
+
+ # Are we a node or DR?
+ my $host_type = $anvil->Get->host_type();
+ my $anvil_uuid = $anvil->Cluster->get_anvil_uuid();
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
+ host_type => $host_type,
+ anvil_uuid => $anvil_uuid,
+ }});
+
+ if (($host_type ne "node") or (not $anvil_uuid))
+ {
+ print "This must be run on a node active in the cluster hosting the server being managed. Exiting.\n";
+ $anvil->nice_exit({exit_code => 1});
+ }
+
+ # Get the Anvil! details.
+ $anvil->Database->get_hosts();
+ $anvil->Database->get_anvils();
+ $anvil->Database->get_storage_group_data({debug => 2});
+
+ # Does this Anvil! have a DR node?
+ if (not $anvil->data->{anvils}{anvil_uuid}{$anvil_uuid}{anvil_dr1_host_uuid})
+ {
+ print "This Anvil! does not seem to have a DR host. Exiting.\n";
+ $anvil->nice_exit({exit_code => 1});
+ }
+
+ # Can we access DR?
+ my $password = $anvil->data->{anvils}{anvil_uuid}{$anvil_uuid}{anvil_password};
+ my $dr_host_uuid = $anvil->data->{anvils}{anvil_uuid}{$anvil_uuid}{anvil_dr1_host_uuid};
+ my $dr_host_name = $anvil->data->{hosts}{host_uuid}{$dr_host_uuid}{host_name};
+ my $dr_ip = $anvil->System->find_matching_ip({
+ debug => 2,
+ host => $dr_host_name,
+ });
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
+ password => $anvil->Log->is_secure($password),
+ dr_host_uuid => $dr_host_uuid,
+ dr_host_name => $dr_host_name,
+ dr_ip => $dr_ip,
+ }});
+ if ((not $dr_ip) or ($dr_ip eq "!!error!!"))
+ {
+ print "Failed to find an IP we can access the DR host: [".$dr_host_name."]. Has it been configured? Is it running? Exiting.\n";
+ $anvil->nice_exit({exit_code => 1});
+ }
+
+ # Test access.
+ my $access = $anvil->Remote->test_access({
+ target => $dr_ip,
+ password => $password,
+ });
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { access => $access }});
+ if (not $access)
+ {
+ print "Failed to access the DR host: [".$dr_host_name."] using the IP: [".$dr_ip."]. Is it running? Exiting.\n";
+ $anvil->nice_exit({exit_code => 1});
+ }
+
+ # Can we parse the CIB?
+ my ($problem) = $anvil->Cluster->parse_cib();
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { problem => $problem }});
+ if ($problem)
+ {
+ print "Failed to parse the CIB. Is this node in the cluster? Exiting.\n";
+ $anvil->nice_exit({exit_code => 1});
+ }
+
+ # Both nodes need to be in the cluster, are they?
+ if (not $anvil->data->{cib}{parsed}{'local'}{ready})
+ {
+ print "We're not a full member of the cluster yet. Please try again once we're fully in. Exiting.\n";
+ $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
+ ### peer to be online.
+ # If we're protecting or removing a server from DR, the peer needs to be up.
+ if ((($anvil->data->{switches}{protect}) or
+ ($anvil->data->{switches}{remove}) or
+ ($anvil->data->{switches}{protocol})) &&
+ (not $anvil->data->{cib}{parsed}{peer}{ready}))
+ {
+ if ($anvil->data->{switches}{protect})
+ {
+ print "We can't setup a server to be protected unless both nodes are up, and the peer isn't at this time. Exiting.\n";
+ }
+ else
+ {
+ print "We can't remove a server from DR unless both nodes are up, and the peer isn't at this time. Exiting.\n";
+ }
+ $anvil->nice_exit({exit_code => 1});
+ }
+
+ # Verify we found the server.
+ $anvil->data->{server}{'server-name'} = "";
+ $anvil->data->{server}{'server-uuid'} = "";
+ $anvil->data->{server}{'anvil-uuid'} = $anvil_uuid;
+ if (not $anvil->data->{switches}{server})
+ {
+ print "Please specify the server to manager using '--server '. Exiting.\n";
+ $anvil->nice_exit({exit_code => 1});
+ }
+ else
+ {
+ my $server = $anvil->data->{switches}{server};
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { server => $server }});
+
+ $anvil->Database->get_servers();
+ if (exists $anvil->data->{servers}{server_uuid}{$server})
+ {
+ $anvil->data->{server}{'server-uuid'} = $server;
+ $anvil->data->{server}{'server-name'} = $anvil->data->{servers}{server_uuid}{$server}{server_name};
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
+ 'server::server-uuid' => $anvil->data->{server}{'server-uuid'},
+ 'server::server-name' => $anvil->data->{server}{'server-name'},
+ }});
+ }
+ if (exists $anvil->data->{servers}{anvil_uuid}{$anvil_uuid}{server_name}{$server})
+ {
+ $anvil->data->{server}{'server-name'} = $server;
+ $anvil->data->{server}{'server-uuid'} = $anvil->data->{servers}{anvil_uuid}{$anvil_uuid}{server_name}{$server}{server_uuid};
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
+ 'server::server-uuid' => $anvil->data->{server}{'server-uuid'},
+ 'server::server-name' => $anvil->data->{server}{'server-name'},
+ }});
+ }
+ }
+
+ # Get and parse the server's definition to find the DRBD devices.
+ if ((not $anvil->data->{server}{'server-uuid'}) or (not $anvil->data->{server}{'server-name'}))
+ {
+ print "Failed to find the server: [".$anvil->data->{switches}{server}."] by name or UUID? Exiting.\n";
+ $anvil->nice_exit({exit_code => 1});
+ }
+
+ if (not $anvil->data->{switches}{protocol})
+ {
+ $anvil->data->{switches}{protocol} = "async";
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
+ 'switches::protocol' => $anvil->data->{switches}{protocol},
+ }});
+ }
+ elsif (($anvil->data->{switches}{protocol} ne "sync") &&
+ ($anvil->data->{switches}{protocol} ne "async") &&
+ ($anvil->data->{switches}{protocol} ne "long-throw"))
+ {
+ print "The protocol: [".$anvil->data->{switches}{protocol}."] is invalid. Please use '--help' for more information.\n";
+ $anvil->nice_exit({exit_code => 1});
+ }
+
+ # Are we being asked to actuall do something?
+ if (((not $anvil->data->{switches}{'connect'}) &&
+ (not $anvil->data->{switches}{disconnect}) &&
+ (not $anvil->data->{switches}{protect}) &&
+ (not $anvil->data->{switches}{remove}) &&
+ (not $anvil->data->{switches}{update})) or
+ ($anvil->data->{switches}{help}) or
+ ($anvil->data->{switches}{h}))
+ {
+ print "
+What do you want to do?
+
+Options (all require --server );
+
+ --connect
+
+ Connect a server already on DR to it's DR copy, update the data there if needed and begin streaming
+ replication.
+
+ --disconnect
+
+ Disconnect a server from the DR image. This will end streaming replication.
+
+ --protect
+
+ The sets up the server to be imaged on DR, if it isn't already protected.
+
+ Notes: If the server is not running, the DRBD resource volume(s) will be brought up. Both nodes need
+ to be online and in the cluster.
+
+ --protocol , default 'async'
+
+ This allows the protocol used to replicate data to the DR host to be configured. By default, 'async'
+ is used.
+
+ Modes:
+
+ async (default)
+
+ This tells the storage layer to consider the write to be completed once the data is on the
+ active node's network transmit buffer. In this way, the DR host is allowed to fall behind a
+ small amount, but the active nodes will not slow down because of higher network transit times
+ to the DR location.
+
+ NOTE: The transmit (TX) buffer size can be checked / updated with 'ethtool -g '.
+ If the transmit buffer fills, storage will hold until the buffer flushes, causing
+ periodic storage IO waits. You can increase the buffer size to a certain degree with
+ 'ethtool -G tx ' (set on all storage network link devices on both
+ nodes. For more information, see:
+
+ https://www.linuxjournal.com/content/queueing-linux-network-stack
+
+ or
+
+ https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/configuring_and_managing_networking/monitoring-and-tuning-the-rx-ring-buffer_configuring-and-managing-networking
+
+ If you set the maximum transmit buffer size and still run into IO waits, consider
+ 'long-throw'.
+
+ sync
+
+ This tells the storage layer to consider the write complete when the data has reached the DR
+ host's storage (when the data is committed to disk on DR). This means that the DR host will
+ never fall behind. However, if the DR's network latency is higher or the bandwidth to the DR
+ is lower than that of the latency/bandwidth between the nodes, then total storage performance
+ will be reduced to DR network speeds while DR is connected.
+
+ This should be tested before implemented in production.
+
+ long-throw
+
+ This is an option that requires an additional license fee to use.
+
+ This option (based on LINBIT's DRBD Proxy) and is designed for DR hosts that are connected
+ over a wide-area network (or other cases where the connection to the DR is high-latency, low
+ bandwidth or intermittently interrupted). It uses RAM on the host to act, effectively, as a
+ very large transmit buffer. This requires allocating host RAM to the task, and so could
+ reduces the available RAM assignable to assign to servers.
+
+ In this mode, the DR host is allowed to fall further behind production, but it significantly
+ reduces (hopefully eliminates) how often node replication waits because of a full transmit
+ buffer.
+
+ The default size is 16 MiB, with a maximum size of 16 GiB. When the size is set to over
+ 1 GiB, the size allocated to this buffer is accounted for when calculating available RAM that
+ can be assigned to hosted servers.
+
+ --remove
+
+ This removes the DR image from the DR host for the server, freeing up space on DR but removing the
+ protection afforded by DR.
+
+ --update
+
+ This tells the DR to be connected and sync, Once the volume(s) on DR are 'UpToDate', the connection
+ is closed. This provides a point in time update of the server's image on DR.
+
+ --Yes
+
+ Note the capital 'Y'. This can be set to proceed without confirmation. Use carefully with '--protect'
+ and '--remove'! If the '--job-uuid' is set, this is assumed and no prompt will be presented.
+
+Exiting.
+";
+ if (($anvil->data->{switches}{help}) or ($anvil->data->{switches}{h}))
+ {
+ $anvil->nice_exit({exit_code => 0});
+ }
+ else
+ {
+ $anvil->nice_exit({exit_code => 1});
+ }
+ }
+
+ # If we're protecting, make sure there's enough space on the DR host.
+ if ($anvil->data->{switches}{protect})
+ {
+ prepare_for_protect($anvil);
+ }
+
+ return(0);
+}
+
+sub prepare_for_protect
+{
+ my ($anvil) = @_;
+
+ # Parse out the DRBD resource's backing the server and get their LV sizes.
+ $anvil->Database->get_server_definitions();
+ my $anvil_uuid = $anvil->Cluster->get_anvil_uuid();
+ my $dr_host_uuid = $anvil->data->{anvils}{anvil_uuid}{$anvil_uuid}{anvil_dr1_host_uuid};
+ my $dr_host_name = $anvil->data->{hosts}{host_uuid}{$dr_host_uuid}{host_name};
+ my $server_name = $anvil->data->{server}{'server-name'};
+ my $server_uuid = $anvil->data->{server}{'server-uuid'};
+ my $short_host_name = $anvil->Get->short_host_name();
+ my $server_definition_xml = $anvil->data->{server_definitions}{server_definition_server_uuid}{$server_uuid}{server_definition_xml};
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
+ anvil_uuid => $anvil_uuid,
+ dr_host_uuid => $dr_host_uuid,
+ dr_host_name => $dr_host_name,
+ server_name => $server_name,
+ server_uuid => $server_uuid,
+ server_definition_xml => $server_definition_xml,
+ short_host_name => $short_host_name,
+ }});
+
+ $anvil->Server->parse_definition({
+ debug => 2,
+ host => $short_host_name,
+ server => $anvil->data->{server}{'server-name'},
+ source => "from_db",
+ definition => $server_definition_xml,
+ });
+
+ $anvil->DRBD->gather_data();
+
+ my $server_ram = $anvil->data->{server}{$short_host_name}{$server_name}{'from_db'}{memory};
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
+ server_ram => $anvil->Convert->add_commas({number => $server_ram})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $server_ram}).")",
+ }});
+ foreach my $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 => { resource => $resource }});
+
+ foreach my $this_host_name (sort {$a cmp $b} keys %{$anvil->data->{new}{resource}{$resource}{host}})
+ {
+ my $this_host_uuid = $anvil->Get->host_uuid_from_name({host_name => $this_host_name});
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
+ this_host_name => $this_host_name,
+ this_host_uuid => $this_host_uuid,
+ }});
+
+ foreach my $volume (sort {$a cmp $b} keys %{$anvil->data->{new}{resource}{$resource}{host}{$this_host_name}{volume}})
+ {
+ # Always get the LV sizes, as that factors metadata. DRBD size is
+ # minus metadata, and 0 when down.
+ my $device_path = $anvil->data->{new}{resource}{$resource}{host}{$this_host_name}{volume}{$volume}{device_path};
+ my $backing_disk = $anvil->data->{new}{resource}{$resource}{host}{$this_host_name}{volume}{$volume}{backing_disk};
+ my $device_minor = $anvil->data->{new}{resource}{$resource}{host}{$this_host_name}{volume}{$volume}{device_minor};
+ my $tcp_port = $anvil->data->{new}{resource}{$resource}{peer}{$this_host_name}{tcp_port};
+ my $this_size = $anvil->Storage->get_size_of_block_device({host_uuid => $this_host_uuid, path => $backing_disk});
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
+ "s1:volume" => $volume,
+ "s2:device_path" => $device_path,
+ "s3:backing_disk" => $backing_disk,
+ "s4:device_minor" => $device_minor,
+ "s5:this_size" => $anvil->Convert->add_commas({number => $this_size})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $this_size}).")",
+ }});
+
+ if ((not exists $anvil->data->{server}{drbd}{$resource}{$volume}{size}) or (not $anvil->data->{server}{drbd}{$resource}{$volume}{size}))
+ {
+ $anvil->data->{server}{drbd}{$resource}{$volume}{size} = $this_size;
+
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
+ "server::drbd::${resource}::${volume}::size" => $anvil->data->{server}{drbd}{$resource}{$volume}{size},
+ }});
+ }
+
+ if (not exists $anvil->data->{server}{drbd}{$resource}{$volume}{storage_group_uuid})
+ {
+ $anvil->data->{server}{drbd}{$resource}{$volume}{storage_group_uuid} = "";
+ }
+
+ ### NOTE: This check make sense only under the assumption that the DRBD minor
+ ### is common across both nodes. This should be the case, but doesn't
+ ### strictly have to be so.
+ if ((not exists $anvil->data->{server}{drbd}{$resource}{$volume}{minor_number}) or
+ (not defined $anvil->data->{server}{drbd}{$resource}{$volume}{minor_number}) or
+ ($anvil->data->{server}{drbd}{$resource}{$volume}{minor_number} eq ""))
+ {
+ $anvil->data->{server}{drbd}{$resource}{$volume}{minor_number} = $device_minor;
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
+ "server::drbd::${resource}::${volume}::minor_number" => $anvil->data->{server}{drbd}{$resource}{$volume}{minor_number},
+ }});
+ }
+
+ if ((not exists $anvil->data->{server}{drbd}{$resource}{$volume}{tcp_port}) or
+ (not defined $anvil->data->{server}{drbd}{$resource}{$volume}{tcp_port}) or
+ ($anvil->data->{server}{drbd}{$resource}{$volume}{tcp_port} eq ""))
+ {
+ $anvil->data->{server}{drbd}{$resource}{$volume}{tcp_port} = $tcp_port;
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
+ "server::drbd::${resource}::${volume}::tcp_port" => $anvil->data->{server}{drbd}{$resource}{$volume}{tcp_port},
+ }});
+ }
+
+ # What storage group does this belong to?
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
+ "server::drbd::${resource}::${volume}::storage_group_uuid" => $anvil->data->{server}{drbd}{$resource}{$volume}{storage_group_uuid},
+ }});
+ if (not $anvil->data->{server}{drbd}{$resource}{$volume}{storage_group_uuid})
+ {
+ my $storage_key = $resource."/".$volume;
+ my $storage_group_uuid = $anvil->Storage->get_storage_group_from_path({
+ debug => 2,
+ anvil_uuid => $anvil_uuid,
+ path => $backing_disk,
+ });
+ my $storage_group_name = $anvil->data->{storage_groups}{anvil_uuid}{$anvil_uuid}{storage_group_uuid}{$storage_group_uuid}{group_name};
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
+ storage_key => $storage_key,
+ storage_group_uuid => $storage_group_uuid,
+ storage_group_name => $storage_group_name,
+ }});
+
+ # We'll need to sum up the volumes on each storage group, as
+ # it's possible the volumes are on different SGs.
+ $anvil->data->{server}{drbd}{$resource}{$volume}{storage_group_uuid} = $storage_group_uuid;
+ $anvil->data->{server}{storage_groups}{$storage_group_name}{used_by}{$storage_key} = 1;
+ $anvil->data->{server}{storage_groups}{$storage_group_name}{storage_group_uuid} = $storage_group_uuid;
+
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
+ "server::drbd::${resource}::${volume}::storage_group_uuid" => $anvil->data->{server}{drbd}{$resource}{$volume}{storage_group_uuid},
+ "server::storage_groups::${storage_group_name}::used_by::${storage_key}" => $anvil->data->{server}{storage_groups}{$storage_group_name}{used_by}{$storage_key},
+ "server::storage_groups::${storage_group_name}::storage_group_uuid" => $anvil->data->{server}{storage_groups}{$storage_group_name}{storage_group_uuid},
+ }});
+ }
+
+ if ($this_size > $anvil->data->{server}{drbd}{$resource}{$volume}{size})
+ {
+ $anvil->data->{server}{drbd}{$resource}{$volume}{size} = $this_size;
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
+ "server::drbd::${resource}::${volume}::size" => $anvil->Convert->add_commas({number => $anvil->data->{server}{drbd}{$resource}{$volume}{size}})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{server}{drbd}{$resource}{$volume}{size}}).")",
+ }});
+ }
+ }
+ }
+ }
+
+ # Make sure there is enough space on DR for the volumes under this VM.
+ my $problem = 0;
+ foreach my $storage_group_name (sort {$a cmp $b} keys %{$anvil->data->{server}{storage_groups}})
+ {
+ my $storage_group_uuid = $anvil->data->{server}{storage_groups}{$storage_group_name}{storage_group_uuid};
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
+ storage_group_name => $storage_group_name,
+ storage_group_uuid => $storage_group_uuid,
+ }});
+
+ # First, is this SG on DR?
+ if (not exists $anvil->data->{storage_groups}{anvil_uuid}{$anvil_uuid}{storage_group_uuid}{$storage_group_uuid}{host_uuid}{$dr_host_uuid})
+ {
+ print "The DR host: [".$dr_host_name."] doesn't appear to be storage group: [".$storage_group_name."]. Unable to proceed.\n";
+ $problem = 1;
+ }
+
+ my $space_needed = 0;
+ foreach my $resource_key (sort {$a cmp $b} keys %{$anvil->data->{server}{storage_groups}{$storage_group_name}{used_by}})
+ {
+ my ($resource, $volume) = ($resource_key =~ /^(.*)\/(\d+)$/);
+ my $volume_size = $anvil->data->{server}{drbd}{$resource}{$volume}{size};
+ $space_needed += $volume_size,
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
+ resource_key => $resource_key,
+ resource => $resource,
+ volume => $volume,
+ volume_size => $anvil->Convert->add_commas({number => $volume_size})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $volume_size}).")",
+ space_needed => $anvil->Convert->add_commas({number => $space_needed})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $space_needed}).")",
+ }});
+
+ }
+
+ # Is there enough space on DR?
+ my $space_on_dr = $anvil->data->{storage_groups}{anvil_uuid}{$anvil_uuid}{storage_group_uuid}{$storage_group_uuid}{host_uuid}{$dr_host_uuid}{vg_free};
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
+ space_on_dr => $anvil->Convert->add_commas({number => $space_on_dr})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $space_on_dr}).")",
+ space_needed => $anvil->Convert->add_commas({number => $space_needed})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $space_needed}).")",
+ }});
+ if ($space_needed > $space_on_dr)
+ {
+ print "We need: [".$anvil->Convert->bytes_to_human_readable({'bytes' => $space_needed})." (".$anvil->Convert->add_commas({number => $space_needed})." Bytes)] from the storage group: [".$storage_group_name."], but only: [".$anvil->Convert->bytes_to_human_readable({'bytes' => $space_on_dr})." (".$anvil->Convert->add_commas({number => $space_on_dr})." bytes)] is available on DR. Unable to proceed.\n";
+ $problem = 1;
+ }
+ }
+ if ($problem)
+ {
+ $anvil->nice_exit({exit_code => 1});
+ }
+
+ print "Verified that there is enough space on DR to proceed!\n";
+ print "The connection protocol will be: [".$anvil->data->{switches}{protocol}."]\n";
+ print "The following LV(s) will be created:\n";
+ foreach my $resource (sort {$a cmp $b} keys %{$anvil->data->{server}{drbd}})
+ {
+ foreach my $volume (sort {$a cmp $b} keys %{$anvil->data->{server}{drbd}{$resource}})
+ {
+ print "- Resource: [".$resource."], Volume: [".$volume."]\n";
+ my $lv_size = $anvil->data->{server}{drbd}{$resource}{$volume}{size};
+ my $storage_group_uuid = $anvil->data->{server}{drbd}{$resource}{$volume}{storage_group_uuid};
+ my $dr_lv_name = $resource."_".$volume;
+ my $dr_vg_name = $anvil->Storage->get_vg_name({
+ debug => 3,
+ storage_group_uuid => $storage_group_uuid,
+ host_uuid => $dr_host_uuid,
+ });
+ my $dr_lv_path = "/dev/".$dr_vg_name."/".$dr_lv_name;
+ my $extent_size = $anvil->data->{storage_groups}{storage_group_uuid}{$storage_group_uuid}{host_uuid}{$dr_host_uuid}{vg_extent_size};
+ my $extent_count = int($lv_size / $extent_size);
+ my $shell_call = $anvil->data->{path}{exe}{lvcreate}." -l ".$extent_count." -n ".$dr_lv_name." ".$dr_vg_name." -y";
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
+ "s01:resource" => $resource,
+ "s02:volume" => $volume,
+ "s03:lv_size" => $anvil->Convert->add_commas({number => $lv_size})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $lv_size}).")", ,
+ "s04:storage_group_uuid" => $storage_group_uuid,
+ "s05:dr_lv_name" => $dr_lv_name,
+ "s06:dr_vg_name" => $dr_vg_name,
+ "s07:dr_lv_path" => $dr_lv_path,
+ "s08:extent_size" => $anvil->Convert->add_commas({number => $extent_size})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $extent_size}).")",
+ "s09:extent_count" => $extent_count,
+ "s10:shell_call" => $shell_call,
+ }});
+
+ $anvil->data->{server}{dr}{volumes}{$resource}{$volume}{lvcreate_call} = $shell_call;
+ $anvil->data->{server}{dr}{volumes}{$resource}{$volume}{lv_path} = $dr_lv_path;
+ $anvil->data->{server}{dr}{volumes}{$resource}{$volume}{storage_group_uuid} = $storage_group_uuid;
+ $anvil->data->{server}{dr}{volumes}{$resource}{$volume}{drbd_tcp_port} = $anvil->data->{server}{drbd}{$resource}{$volume}{tcp_port};
+ $anvil->data->{server}{dr}{volumes}{$resource}{$volume}{drbd_minor} = $anvil->data->{server}{drbd}{$resource}{$volume}{minor_number};
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
+ "server::dr::volumes::${resource}::${volume}::lvcreate_call" => $anvil->data->{server}{dr}{volumes}{$resource}{$volume}{lvcreate_call},
+ "server::dr::volumes::${resource}::${volume}::lv_path" => $anvil->data->{server}{dr}{volumes}{$resource}{$volume}{lv_path},
+ "server::dr::volumes::${resource}::${volume}::storage_group_uuid" => $anvil->data->{server}{dr}{volumes}{$resource}{$volume}{storage_group_uuid},
+ "server::dr::volumes::${resource}::${volume}::drbd_tcp_port" => $anvil->data->{server}{dr}{volumes}{$resource}{$volume}{drbd_tcp_port},
+ "server::dr::volumes::${resource}::${volume}::drbd_minor" => $anvil->data->{server}{dr}{volumes}{$resource}{$volume}{drbd_minor},
+ }});
+
+ # Get the VG name that this volume will be created on.
+ print " - The LV: [".$dr_lv_path."] with the size: [".$anvil->Convert->bytes_to_human_readable({'bytes' => $lv_size})." (".$anvil->Convert->add_commas({number => $lv_size})." Bytes)] will be created.\n";
+ }
+ }
+
+ ### NOTE: 'Yes' is set when a job is picked up, so this won't re-register the job.
+ my $record_job = 0;
+ if (not $anvil->data->{switches}{Yes})
+ {
+ # Ask the user to confirm.
+ print "\n- Proceed? [N/y]: ";
+ my $answer = ;
+ chomp $answer;
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { answer => $answer }});
+
+ if ($answer =~ /^y/i)
+ {
+ print "- Thank you, storing job now.\n";
+ $record_job = 1;
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { record_job => $record_job }});
+ }
+ else
+ {
+ print "- Aborting.\n";
+ $anvil->nice_exit({exit_code => 0});
+ }
+ }
+ elsif (not $anvil->data->{switches}{'job-uuid'})
+ {
+ $record_job = 1;
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { record_job => $record_job }});
+ }
+
+ if ($record_job)
+ {
+ my $job_data = "server=".$anvil->data->{switches}{server}."\n";
+ $job_data .= "protect=1\n";
+ $job_data .= "protocol=".$anvil->data->{switches}{protocol}."\n";
+
+ # Register the job with this host
+ my ($job_uuid) = $anvil->Database->insert_or_update_jobs({
+ debug => 2,
+ job_command => $anvil->data->{path}{exe}{'anvil-manage-dr'}.$anvil->Log->switches,
+ job_data => $job_data,
+ job_name => "server::dr",
+ job_title => "job_0356",
+ job_description => "job_0357",
+ job_progress => 0,
+ job_host_uuid => $anvil->Get->host_uuid,
+ });
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { job_uuid => $job_uuid }});
+ $anvil->nice_exit({exit_code => 0});
+ }
+
+ return(0);
+}
+
+sub load_job
+{
+ my ($anvil) = @_;
+
+ $anvil->Job->clear();
+ $anvil->Job->get_job_details();
+ $anvil->Job->update_progress({
+ progress => 1,
+ job_picked_up_by => $$,
+ job_picked_up_at => time,
+ message => "message_0263",
+ });
+
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
+ "jobs::job_command" => $anvil->data->{jobs}{job_command},
+ "jobs::job_data" => $anvil->data->{jobs}{job_data},
+ "jobs::job_progress" => $anvil->data->{jobs}{job_progress},
+ "jobs::job_status" => $anvil->data->{jobs}{job_status},
+ }});
+
+ # Break up the job data into switches.
+ $anvil->data->{switches}{Yes} = 1;
+ foreach my $line (split/\n/, $anvil->data->{jobs}{job_data})
+ {
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { line => $line }});
+ if ($line =~ /(.*?)=(.*)$/)
+ {
+ my $key = $1;
+ my $value = $2;
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
+ key => $key,
+ value => $value,
+ }});
+
+ $anvil->data->{switches}{$key} = $value;
+ $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
+ "switches::${key}" => $anvil->data->{switches}{$key},
+ }});
+ }
+ }
+
+ return(0);
+}