#!/usr/bin/perl
# 
# This scans the cluster by processing the CIB and storing information about nodes, cluster states, etc.
# 
# NOTE: The data stored here is not bound to a given host. As such, node 1 will run this agent and node 2 
#       won't, _except_ when node 1 is offline and only node 2 is up.
# 
# Examples;
# 
# Exit codes;
# 0 = Normal exit.
# 1 = Startup failure (not running as root, no DB, bad file read, etc)
# 2 = Not a cluster member
# 
# TODO: 
# - When a node is lost, update the location constraints to keep the servers on the surviving node when the
#   peer returns.
# 

use strict;
use warnings;
use Anvil::Tools;
use Data::Dumper;
use Text::Diff;

# Disable buffering
$| = 1;

# Prevent a discrepency between UID/GID and EUID/EGID from throwing an error.
$< = $>;
$( = $);

my $THIS_FILE           =  ($0 =~ /^.*\/(.*)$/)[0];
my $running_directory   =  ($0 =~ /^(.*?)\/$THIS_FILE$/)[0];
if (($running_directory =~ /^\./) && ($ENV{PWD}))
{
	$running_directory =~ s/^\./$ENV{PWD}/;
}

my $anvil = Anvil::Tools->new();

# Make sure we're running as 'root'
# $< == real UID, $> == effective UID
if (($< != 0) && ($> != 0))
{
	# Not root
	print $anvil->Words->string({key => "error_0005"})."\n";
	$anvil->nice_exit({exit_code => 1});
}

$anvil->data->{scancore}{'scan-cluster'}{disable} = 0;
$anvil->Storage->read_config();

# Read switches
$anvil->Get->switches({list => ["purge"], man => $THIS_FILE});

# Handle start-up tasks
my $problem = $anvil->ScanCore->agent_startup({agent => $THIS_FILE});
if ($problem)
{
	$anvil->nice_exit({exit_code => 1});
}

$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0115", variables => { program => $THIS_FILE }});
if ($anvil->data->{switches}{purge})
{
	# This can be called when doing bulk-database purges.
	my $schema_file = $anvil->data->{path}{directories}{scan_agents}."/".$THIS_FILE."/".$THIS_FILE.".sql";
	$anvil->Database->purge_data({
		debug  => 2,
		tables => $anvil->Database->get_tables_from_schema({schema_file => $schema_file}),
	});
	$anvil->nice_exit({exit_code => 0});
}

# Before we do anything, are we a node in a pacemaker cluster?
my $host_type = $anvil->Get->host_type;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { host_type => $host_type }});
if ($host_type ne "node")
{
	$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "scan_cluster_log_0002", variables => { host_type => $host_type }});
	$anvil->nice_exit({exit_code => 0});
}

# Read last scan
read_last_scan($anvil);

# Read and process in one shot.
collect_data($anvil);

# Find changes.
find_changes($anvil);

# Check the cluster config.
check_config($anvil);

# Check the fence delay
check_fence_delay($anvil);

# Check for failed resources or resources that need updates
check_resources($anvil);

# Check for stale data in the CIB and clean up any that are found
cib_cleanup($anvil);

# Shut down.
$anvil->ScanCore->agent_shutdown({agent => $THIS_FILE});


#############################################################################################################
# Functions                                                                                                 #
#############################################################################################################


sub cib_cleanup
{
	my ($anvil) = @_;
	
	my $problem = $anvil->Cluster->parse_cib({debug => 2});
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { problem => $problem }});
	if ($problem)
	{
		# Not in a cluster
		return(0);
	}
	
	# Find the servers still on the cluster
	foreach my $server_name (sort {$a cmp $b} keys %{$anvil->data->{cib}{parsed}{data}{server}})
	{
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { server_name => $server_name }});
		if (exists $anvil->data->{cib}{parsed}{data}{server}{$server_name}{drbd_fence_rule}{'exists'})
		{
			my $attribute = $anvil->data->{cib}{parsed}{data}{server}{$server_name}{drbd_fence_rule}{attribute};
			my $operation = $anvil->data->{cib}{parsed}{data}{server}{$server_name}{drbd_fence_rule}{operation};
			my $value     = $anvil->data->{cib}{parsed}{data}{server}{$server_name}{drbd_fence_rule}{value};
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
				server_name => $server_name,
				attribute   => $attribute, 
				operation   => $operation, 
				value       => $value,
			}});
		}
	}
	
	my $host_name       = $anvil->Get->host_name;
	my $short_host_name = $anvil->Get->short_host_name;
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
		host_name       => $host_name, 
		short_host_name => $short_host_name, 
	}});
	
	my $node_id   = "";
	my $node_name = "";
	foreach my $node (sort {$a cmp $b} keys %{$anvil->data->{cib}{parsed}{data}{node}})
	{
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { node => $node }});
		if (($node eq $short_host_name) or ($node eq $host_name))
		{
			$node_id   = $anvil->data->{cib}{parsed}{data}{node}{$node}{id};
			$node_name = $anvil->data->{cib}{parsed}{configuration}{nodes}{$node_id}{uname};
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
				node_id   => $node_id, 
				node_name => $node_name,
			}});
			last;
		}
	}
	if ($node_id eq "")
	{
		# Node ID for this node was not found!
		return(0);
	}
	
	foreach my $attribute_id (sort {$a cmp $b} keys %{$anvil->data->{cib}{parsed}{cib}{node_state}{$node_id}})
	{
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { attribute_id => $attribute_id }});
		if ($attribute_id =~ /^drbd-fenced_(.*)$/)
		{
			my $server_name = $1;
			my $state       = $anvil->data->{cib}{parsed}{cib}{node_state}{$node_id}{$attribute_id};
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
				server_name => $server_name,
				'state'     => $state, 
			}});
			
			if (not exists $anvil->data->{cib}{parsed}{data}{server}{$server_name})
			{
				# Stale attribute, remove it!
				$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "scan_cluster_log_0012", variables => { attribute => $attribute_id }});
				
				my $shell_call = $anvil->data->{path}{exe}{pcs}." node attribute ".$node_name." ".$attribute_id."=";
				$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call }});
				
				my ($output, $return_code) = $anvil->System->call({shell_call => $shell_call});
				$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
					output      => $output,
					return_code => $return_code, 
				}});
			}
		}
	}
	
	return(0);
}

### TODO: Rework this to use Server->connect_to_libvirtd and phase out Server->find().
# This looks for failed resource and, if found, tries to recover them.
sub check_resources
{
	my ($anvil) = @_;
	
	my ($problem) = $anvil->Cluster->parse_cib({debug => 2});
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { problem => $problem }});
	
	foreach my $server (sort {$a cmp $b} keys %{$anvil->data->{crm_mon}{parsed}{'pacemaker-result'}{resources}{resource}})
	{
		# This is used for alerts, if needed below.
		my $variables = { server => $server };
		
		my $failed = check_if_server_failed($anvil, $server);
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
			server => $server,
			failed => $failed, 
		}});
		if ($failed)
		{
			# Who am I and who is my peer? See if the server is running on either host.
			$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "scan_cluster_log_0003", variables => { server => $server }});
			my $attempt_recovery = 0;
			my $server_found     = 0;
			my $both_nodes_ready = 1;
			foreach my $target ("local", "peer")
			{
				my $node_ready = $anvil->data->{cib}{parsed}{$target}{ready};
				my $node_name  = $anvil->data->{cib}{parsed}{$target}{name};
				my $host_uuid  = $anvil->Get->host_uuid_from_name({host_name => $node_name});
				$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "scan_cluster_log_0004", variables => { 
					node_name  => $node_name,
					host_uuid  => $host_uuid, 
					node_ready => $node_ready, 
				}});
				if (not $node_ready)
				{
					$both_nodes_ready = 1;
				}
				
				if ($host_uuid eq $anvil->Get->host_uuid)
				{
					# Search for the server here
					$anvil->Server->find({debug => 2});
					$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "scan_cluster_log_0005"});
				}
				else
				{
					# Search for the server on the peer.
					my ($target_ip, $target_network) = $anvil->Network->find_target_ip({host_uuid => $host_uuid});
					$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
						target_ip      => $target_ip,
						target_network => $target_network,
					}});
					$anvil->Server->find({
						debug  => 2,
						target => $target_ip,
					});
				}
				my $server_host   = defined $anvil->data->{server}{location}{$server}{host_name} ? $anvil->data->{server}{location}{$server}{host_name} : "";
				my $server_status = defined $anvil->data->{server}{location}{$server}{status}    ? $anvil->data->{server}{location}{$server}{status}    : "";
				$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 1, list => { 
					server_host   => $server_host, 
					server_status => $server_status, 
				}});
				if ($server_host)
				{
					$server_found = 1;
					$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { server_found => $server_found }});
					if (($node_ready) && ($host_uuid eq $anvil->Get->host_uuid))
					{
						# Go ahead with recovery
						$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "scan_cluster_log_0007"});
						$attempt_recovery = 1;
						$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { attempt_recovery => $attempt_recovery }});
					}
				}
			}
			
			if ((not $server_found) && ($both_nodes_ready))
			{
				$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "scan_cluster_log_0008"});
				$attempt_recovery = 1;
				$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { attempt_recovery => $attempt_recovery }});
			}
			elsif (($server_found) && (not $attempt_recovery))
			{
				# The server was found to be running, but not here (or this node is not fully in the cluster). NOT attempting recovery yet.
				$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "scan_cluster_log_0009"});
			}
			
			if ($attempt_recovery)
			{
				$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "scan_cluster_log_0010"});
				$anvil->Cluster->recover_server({
					debug   => 2,
					server  => $server,
					running => $server_found,
				});
				
				# It'll leave 'failed state' for a bit, so we need to wait.
				sleep 3;
				my $wait_until = time + 10;
				my $waiting    = 1;
				$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "scan_cluster_log_0011"});
				while($waiting)
				{
					my $failed = check_if_server_failed($anvil, $server);
					$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { failed => $failed }});
					if ($failed)
					{
						# No luck...
						   $waiting = 0;
						my $changed = $anvil->Alert->check_alert_sent({
							record_locator => "scan_cluster::failed_server::".$server,
							set_by         => $THIS_FILE,
						});
						$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
							waiting => $waiting, 
							changed => $changed,
						}});
						$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "scan_cluster_alert_0014", variables => $variables});
						if ($changed)
						{
							# Send an alert.
							$anvil->Alert->register({debug => 2, alert_level => "notice", message => "scan_cluster_alert_0014", variables => $variables, set_by => $THIS_FILE});
						}
					}
					elsif (time > $wait_until)
					{
						# Success!
						$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "scan_cluster_alert_0013", variables => $variables});
						$anvil->Alert->register({debug => 2, alert_level => "notice", message => "scan_cluster_alert_0013", variables => $variables, set_by => $THIS_FILE});
						
						# Clear the alert, if it existed before
						   $waiting = 0;
						my $changed = $anvil->Alert->check_alert_sent({
							record_locator => "scan_cluster::failed_server::".$server,
							set_by         => $THIS_FILE,
							clear          => 1,
						});
					}
					else
					{
						# Wait a sec
						sleep 2;
					}
				}
			}
		}
		else
		{
			# Make sure that this server wasn't previously failed.
			my $changed = $anvil->Alert->check_alert_sent({
				record_locator => "scan_cluster::failed_server::".$server,
				set_by         => $THIS_FILE,
				clear          => 1,
			});
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { changed => $changed }});
			if ($changed)
			{
				# Send the All-good alert.
				$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "scan_cluster_alert_0015", variables => $variables});
				$anvil->Alert->register({debug => 2, alert_level => "notice", message => "scan_cluster_alert_0015", variables => $variables, set_by => $THIS_FILE});
			}
			
			# Check to see if server's parameters need updating
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { server => $server }});
			my $update = 0;
			foreach my $op (sort {$a cmp $b} keys %{$anvil->data->{cib}{parsed}{cib}{resources}{primitive}{$server}{operations}{op}})
			{
				my $name = $anvil->data->{cib}{parsed}{cib}{resources}{primitive}{$server}{operations}{op}{$op}{name};
				$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { name => $name }});
				if (($name eq "migrate_to") or ($name eq "migrate_from"))
				{
					my $on_fail = exists $anvil->data->{cib}{parsed}{cib}{resources}{primitive}{$server}{operations}{op}{$op}{'on-fail'} ? $anvil->data->{cib}{parsed}{cib}{resources}{primitive}{$server}{operations}{op}{$op}{'on-fail'} : "";
					my $timeout = exists $anvil->data->{cib}{parsed}{cib}{resources}{primitive}{$server}{operations}{op}{$op}{timeout}   ? $anvil->data->{cib}{parsed}{cib}{resources}{primitive}{$server}{operations}{op}{$op}{timeout}   : 0;
					$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
						on_fail => $on_fail, 
						timeout => $timeout,
					}});
					if (($on_fail ne "block") or ($timeout != 600))
					{
						$update = 1;
						$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { update => $update }});
					}
				}
			}
				
			if ($update)
			{
				# Update the resource config.
				my $resource_command = $anvil->data->{path}{exe}{pcs}." resource update ".$server." ocf:alteeve:server name=\"".$server."\" meta allow-migrate=\"true\" op monitor interval=\"60\" start timeout=\"60\" on-fail=\"block\" stop timeout=\"300\" on-fail=\"block\" migrate_to timeout=\"600\" on-fail=\"block\" migrate_from timeout=\"600\" on-fail=\"block\"";
				$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0739", variables => { 
					server  => $server,
					command => $resource_command, 
				}});

				my ($output, $return_code) = $anvil->System->call({shell_call => $resource_command});
				$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
					output      => $output,
					return_code => $return_code, 
				}});
			}
		}
	}
	
	return(0);
}

sub check_if_server_failed
{
	my ($anvil, $server) = @_;
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { server => $server }});
	
	$anvil->Cluster->parse_crm_mon({debug => 2});
	my $failed = exists $anvil->data->{crm_mon}{parsed}{'pacemaker-result'}{resources}{resource}{$server}{variables}{failed} ? $anvil->data->{crm_mon}{parsed}{'pacemaker-result'}{resources}{resource}{$server}{variables}{failed} : 0;
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { failed => $failed }});
	if ($failed eq "true")
	{
		$failed = 1;
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { failed => $failed }});
	}
	elsif ($failed eq "false")
	{
		$failed = 0;
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { failed => $failed }});
	}
	
	return($failed);
}

# Check to see if we need to move the fence delay.
sub check_fence_delay
{
	my ($anvil) = @_;
	
	my $preferred_node = $anvil->Cluster->manage_fence_delay();
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { preferred_node => $preferred_node }});
	if ($preferred_node ne "!!error!!")
	{
		### NOTE: We don't make the peer be the preferred node, a node can only make itself the preferred 
		###       node.
		# How many servers are running on each node.
		$anvil->Database->get_anvils();
		$anvil->Database->get_servers();
		$anvil->Cluster->get_peers();
		my $anvil_uuid      = $anvil->Cluster->get_anvil_uuid();
		my $local_node_is   = $anvil->data->{sys}{anvil}{i_am};
		my $local_node_name = $anvil->data->{cib}{parsed}{'local'}{name};
		my $local_host_name = $anvil->data->{sys}{anvil}{$local_node_is}{host_name};
		my $local_host_uuid = $anvil->data->{sys}{anvil}{$local_node_is}{host_uuid};
		my $peer_node_is    = $anvil->data->{sys}{anvil}{peer_is};
		my $peer_node_name  = $anvil->data->{cib}{parsed}{peer}{name};;
		my $peer_host_name  = $anvil->data->{sys}{anvil}{$peer_node_is}{host_name};
		my $peer_host_uuid  = $anvil->data->{sys}{anvil}{$peer_node_is}{host_uuid};
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
			anvil_uuid      => $anvil_uuid,
			local_node_is   => $local_node_is, 
			local_node_name => $local_node_name, 
			local_host_name => $local_host_name, 
			local_host_uuid => $local_host_uuid, 
			peer_node_is    => $peer_node_is, 
			peer_node_name  => $peer_node_name, 
			peer_host_name  => $peer_host_name, 
			peer_host_uuid  => $peer_host_uuid, 
		}});
		
		# Get the short host names, as that's usually what the node name is.
		my $local_short_host_name =  $local_host_name;
		$local_short_host_name =~ s/\..$//;
		my $peer_short_host_name  =  $peer_host_name;
		$peer_short_host_name  =~ s/\..$//;
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
			local_short_host_name => $local_short_host_name, 
			peer_short_host_name  => $peer_short_host_name, 
		}});
		
		# If my peer isn't in the cluster, make sure I am the fence delay host.
		if (not $anvil->data->{cib}{parsed}{peer}{ready})
		{
			# My peer is not ready, make sure I'm the preferred host.
			if (($preferred_node eq $local_node_name) or ($preferred_node eq $local_host_name) && ($preferred_node eq $local_short_host_name))
			{
				# We're good.
				$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0633"});
			}
			else
			{
				# We're not, set the delay to us.
				$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0634"});
				my $preferred_node = $anvil->Cluster->manage_fence_delay({prefer => $local_node_name});
				return(0);
			}
		}
		
		# How many servers are on each node?
		my $local_server_count = 0;
		my $peer_server_count  = 0;
		foreach my $server_uuid (keys %{$anvil->data->{servers}{server_uuid}})
		{
			next if $anvil_uuid ne $anvil->data->{servers}{server_uuid}{$server_uuid}{server_anvil_uuid};
			
			my $server_name      = $anvil->data->{servers}{server_uuid}{$server_uuid}{server_name};
			my $server_state     = $anvil->data->{servers}{server_uuid}{$server_uuid}{server_state};
			my $server_host_uuid = $anvil->data->{servers}{server_uuid}{$server_uuid}{server_host_uuid};
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
				server_uuid      => $server_uuid, 
				server_name      => $server_name, 
				server_state     => $server_state, 
				server_host_uuid => $server_host_uuid, 
			}});
			next if $server_state eq "shut off";
			if ($server_state eq "migrating")
			{
				# Don't do anything.
				$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0635", variables => { server_name => $server_name }});
				return(0);
			}
			if ($server_host_uuid eq $local_host_uuid)
			{
				$local_server_count++;
				$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { local_server_count => $local_server_count }});
			}
			elsif ($server_host_uuid eq $peer_host_uuid)
			{
				$peer_server_count++;
				$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { peer_server_count => $peer_server_count }});
			}
		}
		
		# Don't do anything if there are no servers running anywhere, or if both servers have at least one 
		# server.
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
			local_server_count => $local_server_count, 
			peer_server_count  => $peer_server_count, 
		}});
		if ((not $local_server_count) && (not $peer_server_count))
		{
			$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0636"});
			return(0);
		}
		elsif (($local_server_count) && ($peer_server_count))
		{
			$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0637", variables => {
				local_server_count => $local_server_count,
				peer_server_count  => $peer_server_count,
			}});
			return(0);
		}
		elsif (($local_server_count) && ($preferred_node ne $local_node_name))
		{
			# Make us the preferred host.
			$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0638"});
			my $preferred_node = $anvil->Cluster->manage_fence_delay({
				debug  => 2,
				prefer => $local_node_name,
			});
			return(0);
		}
	}
	
	return(0);
}

sub check_config
{
	my ($anvil) = @_;
	
	$anvil->Database->get_manifests();
	my $anvil_name    = $anvil->Cluster->get_anvil_name({});
	my $manifest_uuid = $anvil->data->{manifests}{manifest_name}{$anvil_name}{manifest_uuid};
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
		anvil_name    => $anvil_name,
		manifest_uuid => $manifest_uuid, 
	}});
	
	if ($manifest_uuid)
	{
		# Check to see if the stonith config needs to be updated.
		$anvil->Cluster->check_stonith_config({debug => 2});
	}
	
	return(0);
}

# Looks for changes.
sub find_changes
{
	my ($anvil) = @_;
	
	# We can't track a cluster through name change, so either we're INSERTing a new one, or bust.
	my $scan_cluster_anvil_uuid = $anvil->Cluster->get_anvil_uuid();
	my $anvil_name              = $anvil->Get->anvil_name_from_uuid({anvil_uuid => $scan_cluster_anvil_uuid});
	my $scan_cluster_uuid       = "";
	my $cluster_name            = $anvil->data->{cib}{parsed}{data}{cluster}{name};
	my $stonith_enabled         = $anvil->data->{cib}{parsed}{data}{stonith}{enabled};
	my $stonith_max_attempts    = $anvil->data->{cib}{parsed}{data}{stonith}{'max-attempts'};
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
		's1:scan_cluster_anvil_uuid' => $scan_cluster_anvil_uuid, 
		's2:anvil_name'              => $anvil_name, 
		's3:cluster_name'            => $cluster_name, 
		's4:stonith_enabled'         => $stonith_enabled,
		's5:stonith_max_attempts'    => $stonith_max_attempts, 
	}});
	
	# If we're a full cluster member, read the CIB as well.
	my $cluster_cib = "";
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { "cib::parsed::local::ready" => $anvil->data->{cib}{parsed}{'local'}{ready} }});
	if ($anvil->data->{cib}{parsed}{'local'}{ready})
	{
		($cluster_cib, my $return_code) = $anvil->System->call({shell_call => $anvil->data->{path}{exe}{pcs}." cluster cib", source => $THIS_FILE, line => __LINE__});
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
			cluster_cib => $cluster_cib, 
			return_code => $return_code,
		}});
		
		# The update time is per-machine and can be different on subnodes. To prevent cycling CIB changed alerts, break down the <cib ...> attributes. Example;
		# -<cib crm_feature_set="3.19.0" validate-with="pacemaker-3.9" epoch="39" num_updates="50" admin_epoch="0" cib-last-written="Thu Aug 29 13:06:09 2024" update-origin="an-a03n02" update-client="root" have-quorum="1" dc-uuid="2">
		# +<cib crm_feature_set="3.19.0" validate-with="pacemaker-3.9" epoch="39" num_updates="50" admin_epoch="0" cib-last-written="Thu Aug 29 13:06:20 2024" update-origin="an-a03n02" update-client="root" have-quorum="1" dc-uuid="2">
		my $new_cib = "";
		foreach my $line (split/\n/, $cluster_cib)
		{
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { line => $line }});
			
			# These may not exist on both and need to be cut out
			$line =~ s/ cib-last-written="(.*?)"//;
			$line =~ s/ update-user="(.*?)"//;
			
			# These exist on both but can differ, so x out the value
			$line =~ s/ name="last-lrm-refresh" value="(.*?)"/ name="last-lrm-refresh" value="x"/;
			$line =~ s/ call-id="(.*?)"/ call-id="x"/;
			$line =~ s/ last-rc-change="(.*?)"/ last-rc-change="x"/;
			$line =~ s/ exec-time="(.*?)"/ exec-time=""/;
			$line =~ s/ update-client="(.*?)"/ update-client="x"/g;
			
			$new_cib .= $line."\n";
		}
		
		my $difference = diff \$cluster_cib, \$new_cib, { STYLE => 'Unified' };
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { difference => $difference }});
		
		$cluster_cib = $new_cib;
	}
	
	if (exists $anvil->data->{sql}{anvil_uuid}{$scan_cluster_anvil_uuid})
	{
		# Check for a name change
		   $scan_cluster_uuid = $anvil->data->{sql}{anvil_uuid}{$scan_cluster_anvil_uuid};
		my $old_cluster_name  = $anvil->data->{sql}{scan_cluster}{scan_cluster_uuid}{$scan_cluster_uuid}{scan_cluster_name};
		my $old_cluster_cib   = $anvil->data->{sql}{scan_cluster}{scan_cluster_uuid}{$scan_cluster_uuid}{scan_cluster_cib};
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
			scan_cluster_uuid => $scan_cluster_uuid,
			old_cluster_name  => $old_cluster_name, 
			old_cluster_cib   => $old_cluster_cib, 
		}});
		if ($cluster_name ne $old_cluster_name)
		{
			# The name of the cluster has changed.
			my $query = "
UPDATE 
    scan_cluster 
SET 
    scan_cluster_name = ".$anvil->Database->quote($cluster_name).", 
    modified_date     = ".$anvil->Database->quote($anvil->Database->refresh_timestamp)."
WHERE 
    scan_cluster_uuid = ".$anvil->Database->quote($scan_cluster_uuid)."
;";
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }});
			$anvil->Database->write({query => $query, source => $THIS_FILE, line => __LINE__});
			
			my $variables = {
				new_cluster_name => $cluster_name, 
				old_cluster_name => $old_cluster_name, 
			};
			$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "scan_cluster_alert_0002", variables => $variables});
			$anvil->Alert->register({debug => 2, alert_level => "notice", message => "scan_cluster_alert_0002", variables => $variables, set_by => $THIS_FILE});
		}
		if (($cluster_cib) && ($cluster_cib ne $old_cluster_cib))
		{
			my $query = "
UPDATE 
    scan_cluster 
SET 
    scan_cluster_cib  = ".$anvil->Database->quote($cluster_cib).", 
    modified_date     = ".$anvil->Database->quote($anvil->Database->refresh_timestamp)."
WHERE 
    scan_cluster_uuid = ".$anvil->Database->quote($scan_cluster_uuid)."
;";
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }});
			$anvil->Database->write({query => $query, source => $THIS_FILE, line => __LINE__});
			
			my $difference = diff \$old_cluster_cib, \$cluster_cib, { STYLE => 'Unified' };
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { difference => $difference }});
			
			my $variables = {
				cluster_name => $cluster_name, 
				difference   => $difference, 
			};
			$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "scan_cluster_alert_0012", variables => $variables});
			$anvil->Alert->register({debug => 2, alert_level => "notice", message => "scan_cluster_alert_0012", variables => $variables, set_by => $THIS_FILE});
		}
	}
	else
	{
		# New cluster, INSERT
		   $scan_cluster_uuid = $anvil->Get->uuid();
		my $query             = "
INSERT INTO  
    scan_cluster 
(
    scan_cluster_uuid, 
    scan_cluster_anvil_uuid,
    scan_cluster_name, 
    scan_cluster_cib, 
    modified_date
) VALUES (
    ".$anvil->Database->quote($scan_cluster_uuid).", 
    ".$anvil->Database->quote($scan_cluster_anvil_uuid).",
    ".$anvil->Database->quote($cluster_name).", 
    ".$anvil->Database->quote($cluster_cib).", 
    ".$anvil->Database->quote($anvil->Database->refresh_timestamp)."
);";
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }});
		$anvil->Database->write({query => $query, source => $THIS_FILE, line => __LINE__});
		
		my $variables = { cluster_name => $cluster_name };
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "scan_cluster_alert_0001", variables => $variables});
		$anvil->Alert->register({debug => 2, alert_level => "notice", message => "scan_cluster_alert_0001", variables => $variables, set_by => $THIS_FILE});
	}
	
	$anvil->Database->get_anvils();
	foreach my $scan_cluster_node_name (sort {$a cmp $b} keys %{$anvil->data->{cib}{parsed}{data}{node}})
	{
		my $scan_cluster_node_host_uuid = $anvil->Get->host_uuid_from_name({host_name => $scan_cluster_node_name}) // "";
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level  => 2, list => { 
			scan_cluster_node_name      => $scan_cluster_node_name,
			scan_cluster_node_host_uuid => $scan_cluster_node_host_uuid, 
		}});
		if (not $scan_cluster_node_host_uuid)
		{
			# Something is wrong with this host. Does the hostname match to node name?
			$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "scan_cluster_alert_0016", variables => { node_name => $scan_cluster_node_name }});
			next;
		}
		
		my $scan_cluster_node_pacemaker_id     = $anvil->data->{cib}{parsed}{data}{node}{$scan_cluster_node_name}{node_state}{pacemaker_id};
		my $scan_cluster_node_in_ccm           = $anvil->data->{cib}{parsed}{data}{node}{$scan_cluster_node_name}{node_state}{in_ccm};
		my $scan_cluster_node_crmd_member      = $anvil->data->{cib}{parsed}{data}{node}{$scan_cluster_node_name}{node_state}{crmd};
		my $scan_cluster_node_cluster_member   = $anvil->data->{cib}{parsed}{data}{node}{$scan_cluster_node_name}{node_state}{'join'};
		my $scan_cluster_node_maintenance_mode = $anvil->data->{cib}{parsed}{data}{node}{$scan_cluster_node_name}{node_state}{'maintenance-mode'};
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level  => 2, list => { 
			scan_cluster_node_pacemaker_id     => $scan_cluster_node_pacemaker_id, 
			scan_cluster_node_in_ccm           => $scan_cluster_node_in_ccm, 
			scan_cluster_node_crmd_member      => $scan_cluster_node_crmd_member, 
			scan_cluster_node_cluster_member   => $scan_cluster_node_cluster_member, 
			scan_cluster_node_maintenance_mode => $scan_cluster_node_maintenance_mode, 
		}});
		
		if (exists $anvil->data->{sql}{scan_cluster_node_host_uuid}{$scan_cluster_node_host_uuid})
		{
			# Look for changes.
			my $scan_cluster_node_uuid                 = $anvil->data->{sql}{scan_cluster_node_host_uuid}{$scan_cluster_node_host_uuid};
			my $old_scan_cluster_node_name             = $anvil->data->{sql}{scan_cluster_node}{scan_cluster_node_uuid}{$scan_cluster_node_uuid}{scan_cluster_node_name};
			my $old_scan_cluster_node_pacemaker_id     = $anvil->data->{sql}{scan_cluster_node}{scan_cluster_node_uuid}{$scan_cluster_node_uuid}{scan_cluster_node_pacemaker_id};
			my $old_scan_cluster_node_in_ccm           = $anvil->data->{sql}{scan_cluster_node}{scan_cluster_node_uuid}{$scan_cluster_node_uuid}{scan_cluster_node_in_ccm};
			my $old_scan_cluster_node_crmd_member      = $anvil->data->{sql}{scan_cluster_node}{scan_cluster_node_uuid}{$scan_cluster_node_uuid}{scan_cluster_node_crmd_member};
			my $old_scan_cluster_node_cluster_member   = $anvil->data->{sql}{scan_cluster_node}{scan_cluster_node_uuid}{$scan_cluster_node_uuid}{scan_cluster_node_cluster_member};
			my $old_scan_cluster_node_maintenance_mode = $anvil->data->{sql}{scan_cluster_node}{scan_cluster_node_uuid}{$scan_cluster_node_uuid}{scan_cluster_node_maintenance_mode};
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level  => 2, list => { 
				scan_cluster_node_uuid                 => $scan_cluster_node_uuid, 
				old_scan_cluster_node_name             => $old_scan_cluster_node_name,
				old_scan_cluster_node_pacemaker_id     => $old_scan_cluster_node_pacemaker_id, 
				old_scan_cluster_node_in_ccm           => $old_scan_cluster_node_in_ccm, 
				old_scan_cluster_node_crmd_member      => $old_scan_cluster_node_crmd_member, 
				old_scan_cluster_node_cluster_member   => $old_scan_cluster_node_cluster_member, 
				old_scan_cluster_node_maintenance_mode => $old_scan_cluster_node_maintenance_mode, 
			}});

			my $update = 0;
			if ($scan_cluster_node_name ne $old_scan_cluster_node_name)
			{
				$update = 1;
				$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level  => 2, list => { update => $update }});
				
				my $variables = { 
					new_node_name => $scan_cluster_node_name,
					old_node_name => $old_scan_cluster_node_name, 
				};
				$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "scan_cluster_alert_0008", variables => $variables});
				$anvil->Alert->register({debug => 2, alert_level => "notice", message => "scan_cluster_alert_0009", variables => $variables, set_by => $THIS_FILE});
			}
			if ($scan_cluster_node_pacemaker_id ne $old_scan_cluster_node_pacemaker_id)
			{
				$update = 1;
				$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level  => 2, list => { update => $update }});
				
				my $variables = { 
					node_name        => $scan_cluster_node_name,
					new_pacemaker_id => $scan_cluster_node_pacemaker_id     ? "#!string!scan_cluster_unit_0001!#" : "#!string!scan_cluster_unit_0002!#", 
					old_pacemaker_id => $old_scan_cluster_node_pacemaker_id ? "#!string!scan_cluster_unit_0001!#" : "#!string!scan_cluster_unit_0002!#", 
				};
				$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "scan_cluster_alert_0004", variables => $variables});
				$anvil->Alert->register({debug => 2, alert_level => "notice", message => "scan_cluster_alert_0004", variables => $variables, set_by => $THIS_FILE});
			}
			if ($scan_cluster_node_in_ccm ne $old_scan_cluster_node_in_ccm)
			{
				$update = 1;
				$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level  => 2, list => { update => $update }});
				
				my $variables = { 
					node_name  => $scan_cluster_node_name,
					new_in_ccm => $scan_cluster_node_in_ccm     ? "#!string!scan_cluster_unit_0001!#" : "#!string!scan_cluster_unit_0002!#", 
					old_in_ccm => $old_scan_cluster_node_in_ccm ? "#!string!scan_cluster_unit_0001!#" : "#!string!scan_cluster_unit_0002!#", 
				};
				$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "scan_cluster_alert_0005", variables => $variables});
				$anvil->Alert->register({debug => 2, alert_level => "notice", message => "scan_cluster_alert_0005", variables => $variables, set_by => $THIS_FILE});
			}
			if ($scan_cluster_node_crmd_member ne $old_scan_cluster_node_crmd_member)
			{
				$update = 1;
				$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level  => 2, list => { update => $update }});
				
				my $variables = { 
					node_name       => $scan_cluster_node_name,
					new_crmd_member => $scan_cluster_node_crmd_member     ? "#!string!scan_cluster_unit_0001!#" : "#!string!scan_cluster_unit_0002!#", 
					old_crmd_member => $old_scan_cluster_node_crmd_member ? "#!string!scan_cluster_unit_0001!#" : "#!string!scan_cluster_unit_0002!#", 
				};
				$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "scan_cluster_alert_0006", variables => $variables});
				$anvil->Alert->register({debug => 2, alert_level => "notice", message => "scan_cluster_alert_0006", variables => $variables, set_by => $THIS_FILE});
			}
			if ($scan_cluster_node_cluster_member ne $old_scan_cluster_node_cluster_member)
			{
				$update = 1;
				$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level  => 2, list => { update => $update }});
				
				my $variables = { 
					node_name          => $scan_cluster_node_name,
					new_cluster_member => $scan_cluster_node_cluster_member     ? "#!string!scan_cluster_unit_0001!#" : "#!string!scan_cluster_unit_0002!#", 
					old_cluster_member => $old_scan_cluster_node_cluster_member ? "#!string!scan_cluster_unit_0001!#" : "#!string!scan_cluster_unit_0002!#", 
				};
				$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "scan_cluster_alert_0007", variables => $variables});
				$anvil->Alert->register({debug => 2, alert_level => "notice", message => "scan_cluster_alert_0007", variables => $variables, set_by => $THIS_FILE});
			}
			if ($scan_cluster_node_maintenance_mode ne $old_scan_cluster_node_maintenance_mode)
			{
				$update = 1;
				$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level  => 2, list => { update => $update }});
				
				my $variables = { 
					node_name            => $scan_cluster_node_name,
					new_maintenance_mode => $scan_cluster_node_maintenance_mode     ? "#!string!scan_cluster_unit_0001!#" : "#!string!scan_cluster_unit_0002!#", 
					old_maintenance_mode => $old_scan_cluster_node_maintenance_mode ? "#!string!scan_cluster_unit_0001!#" : "#!string!scan_cluster_unit_0002!#", 
				};
				$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "scan_cluster_alert_0008", variables => $variables});
				$anvil->Alert->register({debug => 2, alert_level => "notice", message => "scan_cluster_alert_0008", variables => $variables, set_by => $THIS_FILE});
			}
			
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level  => 2, list => { update => $update }});
			if ($update)
			{
				my $query = "
UPDATE 
    scan_cluster_nodes 
SET 
    scan_cluster_node_name             = ".$anvil->Database->quote($scan_cluster_node_name).", 
    scan_cluster_node_pacemaker_id     = ".$anvil->Database->quote($scan_cluster_node_pacemaker_id).", 
    scan_cluster_node_in_ccm           = ".$anvil->Database->quote($scan_cluster_node_in_ccm).", 
    scan_cluster_node_crmd_member      = ".$anvil->Database->quote($scan_cluster_node_crmd_member).", 
    scan_cluster_node_cluster_member   = ".$anvil->Database->quote($scan_cluster_node_cluster_member).", 
    scan_cluster_node_maintenance_mode = ".$anvil->Database->quote($scan_cluster_node_maintenance_mode).", 
    modified_date                      = ".$anvil->Database->quote($anvil->Database->refresh_timestamp)."
WHERE
    scan_cluster_node_uuid             = ".$anvil->Database->quote($scan_cluster_node_uuid)."
;";
				$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }});
				$anvil->Database->write({query => $query, source => $THIS_FILE, line => __LINE__});
			}
		}
		else
		{
			# Add the node.
			my $scan_cluster_node_uuid = $anvil->Get->uuid();
			my $query                  = "
INSERT INTO 
    scan_cluster_nodes 
(
    scan_cluster_node_uuid, 
    scan_cluster_node_scan_cluster_uuid, 
    scan_cluster_node_host_uuid, 
    scan_cluster_node_name, 
    scan_cluster_node_pacemaker_id, 
    scan_cluster_node_in_ccm, 
    scan_cluster_node_crmd_member, 
    scan_cluster_node_cluster_member, 
    scan_cluster_node_maintenance_mode, 
    modified_date
) VALUES (
    ".$anvil->Database->quote($scan_cluster_node_uuid).", 
    ".$anvil->Database->quote($scan_cluster_uuid).", 
    ".$anvil->Database->quote($scan_cluster_node_host_uuid).", 
    ".$anvil->Database->quote($scan_cluster_node_name).", 
    ".$anvil->Database->quote($scan_cluster_node_pacemaker_id).", 
    ".$anvil->Database->quote($scan_cluster_node_in_ccm).", 
    ".$anvil->Database->quote($scan_cluster_node_crmd_member).", 
    ".$anvil->Database->quote($scan_cluster_node_cluster_member).", 
    ".$anvil->Database->quote($scan_cluster_node_maintenance_mode).", 
    ".$anvil->Database->quote($anvil->Database->refresh_timestamp)."
);";
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }});
			$anvil->Database->write({query => $query, source => $THIS_FILE, line => __LINE__});
			
			my $host_name = $anvil->Get->host_name_from_uuid({host_uuid => $scan_cluster_node_host_uuid});
			my $variables = { 
				cluster_name     => $cluster_name, 
				node_name        => $scan_cluster_node_name,
				host_uuid        => $scan_cluster_node_host_uuid, 
				host_name        => $host_name, 
				pacemaker_id     => $scan_cluster_node_pacemaker_id, 
				in_ccm           => $scan_cluster_node_in_ccm           ? "#!string!scan_cluster_unit_0001!#" : "#!string!scan_cluster_unit_0002!#", # Yes or No
				crmd_member      => $scan_cluster_node_crmd_member      ? "#!string!scan_cluster_unit_0001!#" : "#!string!scan_cluster_unit_0002!#", 
				cluster_member   => $scan_cluster_node_cluster_member   ? "#!string!scan_cluster_unit_0001!#" : "#!string!scan_cluster_unit_0002!#", 
				maintenance_mode => $scan_cluster_node_maintenance_mode ? "#!string!scan_cluster_unit_0001!#" : "#!string!scan_cluster_unit_0002!#", 
			};
			$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "scan_cluster_alert_0003", variables => $variables});
			$anvil->Alert->register({debug => 2, alert_level => "notice", message => "scan_cluster_alert_0003", variables => $variables, set_by => $THIS_FILE});
		}
	}
	
	### TODO: Check for / repair bad cluster config issues
	# If we're still alive, we're either node 1, or we're node 2 and node 1 is not ready. If we're not ready, 
	if ($stonith_max_attempts ne "INFINITY")
	{
		### TODO: Call pcs to update 
	}
	
	return(0);
}

# Read in existing data from the database.
sub read_last_scan
{
	my ($anvil) = @_;
	
	my $anvil_uuid = $anvil->Cluster->get_anvil_uuid();
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { anvil_uuid => $anvil_uuid }});
	if (not $anvil_uuid)
	{
		# If we're not in a node yet, there's nothing to do.
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "scan_cluster_log_0001"});
		$anvil->nice_exit({exit_code => 0});
	}
	
	# Pull our old data.
	my $query = "
SELECT 
    scan_cluster_uuid, 
    scan_cluster_name, 
    scan_cluster_cib, 
    modified_date 
FROM 
    scan_cluster 
WHERE 
    scan_cluster_anvil_uuid = ".$anvil->Database->quote($anvil_uuid)." 
ORDER BY 
    modified_date DESC
;";
	$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 => 3, list => { 
		results => $results, 
		count   => $count, 
	}});
	foreach my $row (@{$results})
	{
		# NOTE: There's no known way to track a cluster name change, so we can't really avoid having 
		#       an entery per cluster name.
		my $scan_cluster_uuid = $row->[0]; 
		my $scan_cluster_name = $row->[1]; 
		my $scan_cluster_cib  = $row->[2];
		my $modified_date     = $row->[3];
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
			"s1:scan_cluster_uuid" => $scan_cluster_uuid,
			"s2:scan_cluster_name" => $scan_cluster_name,
			"s3:scan_cluster_cib"  => $scan_cluster_cib, 
			"s3:modified_date"     => $modified_date, 
		}});
		
		# Store the old data now.
		$anvil->data->{sql}{scan_cluster}{scan_cluster_uuid}{$scan_cluster_uuid}{scan_cluster_name}       = $scan_cluster_name;
		$anvil->data->{sql}{scan_cluster}{scan_cluster_uuid}{$scan_cluster_uuid}{scan_cluster_anvil_uuid} = $anvil_uuid;
		$anvil->data->{sql}{scan_cluster}{scan_cluster_uuid}{$scan_cluster_uuid}{scan_cluster_cib}        = $scan_cluster_cib;
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
			"sql::scan_cluster::scan_cluster_uuid::${scan_cluster_uuid}::scan_cluster_name"       => $anvil->data->{sql}{scan_cluster}{scan_cluster_uuid}{$scan_cluster_uuid}{scan_cluster_name},
			"sql::scan_cluster::scan_cluster_uuid::${scan_cluster_uuid}::scan_cluster_anvil_uuid" => $anvil->data->{sql}{scan_cluster}{scan_cluster_uuid}{$scan_cluster_uuid}{scan_cluster_anvil_uuid},
			"sql::scan_cluster::scan_cluster_uuid::${scan_cluster_uuid}::scan_cluster_cib"        => $anvil->data->{sql}{scan_cluster}{scan_cluster_uuid}{$scan_cluster_uuid}{scan_cluster_cib},
		}});
		
		# If this is deleted, don't make this findable by the anvil uuid.
		next if $scan_cluster_cib eq "DELETED";
		
		# Make it easy to look up the cluster_uuid from the anvil_uuid.
		if (not exists $anvil->data->{sql}{anvil_uuid}{$anvil_uuid})
		{
			$anvil->data->{sql}{anvil_uuid}{$anvil_uuid} = $scan_cluster_uuid;
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
				"sql::anvil_uuid::${anvil_uuid}" => $anvil->data->{sql}{anvil_uuid}{$anvil_uuid},
			}});
		}
		else
		{
			# Duplicate! We sorted by date, so this is an older record, mark it as deleted.
			$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "scan_cluster_log_0013", variables => { scan_cluster_uuid => $scan_cluster_uuid }});
			my $query = "
UPDATE 
    scan_cluster 
SET 
    scan_cluster_cib  = 'DELETED',
    modified_date     = ".$anvil->Database->quote($anvil->Database->refresh_timestamp)."
WHERE 
    scan_cluster_uuid = ".$anvil->Database->quote($scan_cluster_uuid)."
;";
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }});
			$anvil->Database->write({query => $query, source => $THIS_FILE, line => __LINE__});
			
			$anvil->data->{sql}{scan_cluster}{scan_cluster_uuid}{$scan_cluster_uuid}{scan_cluster_cib} = "DELETED";
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
				"sql::scan_cluster::scan_cluster_uuid::${scan_cluster_uuid}::scan_cluster_cib" => $anvil->data->{sql}{scan_cluster}{scan_cluster_uuid}{$scan_cluster_uuid}{scan_cluster_cib},
			}});
		}
	}
	undef $count;
	undef $results;
	
	if (not $anvil->data->{sql}{anvil_uuid}{$anvil_uuid})
	{
		#  Nothing more to load.
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "scan_cluster_log_0014"});
		return(0);
	}
	
	my $scan_cluster_uuid = $anvil->data->{sql}{anvil_uuid}{$anvil_uuid}; 
	   $query             = "
SELECT 
    scan_cluster_node_uuid, 
    scan_cluster_node_host_uuid, 
    scan_cluster_node_name, 
    scan_cluster_node_pacemaker_id, 
    scan_cluster_node_in_ccm, 
    scan_cluster_node_crmd_member, 
    scan_cluster_node_cluster_member, 
    scan_cluster_node_maintenance_mode 
FROM 
    scan_cluster_nodes 
WHERE 
    scan_cluster_node_scan_cluster_uuid = ".$anvil->Database->quote($scan_cluster_uuid)." 
;";
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }});
	
	$results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__});
	$count   = @{$results};
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
		results => $results, 
		count   => $count, 
	}});
	foreach my $row (@{$results})
	{
		# We've got an entry in the 'scan_cluster_nodes' table, so now we'll look for data in the node and 
		# services tables.
		my $scan_cluster_node_uuid              = $row->[0]; 
		my $scan_cluster_node_host_uuid         = $row->[1]; 
		my $scan_cluster_node_name              = $row->[2]; 
		my $scan_cluster_node_pacemaker_id      = $row->[3]; 
		my $scan_cluster_node_in_ccm            = $row->[4]; 
		my $scan_cluster_node_crmd_member       = $row->[5]; 
		my $scan_cluster_node_cluster_member    = $row->[6]; 
		my $scan_cluster_node_maintenance_mode  = $row->[7]; 
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
			's1:scan_cluster_node_uuid'             => $scan_cluster_node_uuid,
			's2:scan_cluster_node_host_uuid'        => $scan_cluster_node_host_uuid,
			's3:scan_cluster_node_name'             => $scan_cluster_node_name,
			's4:scan_cluster_node_pacemaker_id'     => $scan_cluster_node_pacemaker_id,
			's5:scan_cluster_node_in_ccm'           => $scan_cluster_node_in_ccm,
			's6:scan_cluster_node_crmd_member'      => $scan_cluster_node_crmd_member, 
			's7:scan_cluster_node_cluster_member'   => $scan_cluster_node_cluster_member, 
			's8:scan_cluster_node_maintenance_mode' => $scan_cluster_node_maintenance_mode, 
		}});
		
		# If this node belongs to a DELETED cluster, skip it.
		my $scan_cluster_cib = $anvil->data->{sql}{scan_cluster}{scan_cluster_uuid}{$scan_cluster_uuid}{scan_cluster_cib};
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { scan_cluster_cib => $scan_cluster_cib }});
		next if $scan_cluster_cib eq "DELETED";
		
		# Store the old data now.
		$anvil->data->{sql}{scan_cluster_node}{scan_cluster_node_uuid}{$scan_cluster_node_uuid} = {
			scan_cluster_node_scan_cluster_uuid => $scan_cluster_uuid,
			scan_cluster_node_host_uuid         => $scan_cluster_node_host_uuid,
			scan_cluster_node_name              => $scan_cluster_node_name,
			scan_cluster_node_pacemaker_id      => $scan_cluster_node_pacemaker_id,
			scan_cluster_node_in_ccm            => $scan_cluster_node_in_ccm,
			scan_cluster_node_crmd_member       => $scan_cluster_node_crmd_member, 
			scan_cluster_node_cluster_member    => $scan_cluster_node_cluster_member, 
			scan_cluster_node_maintenance_mode  => $scan_cluster_node_maintenance_mode, 
		};
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
			"sql::scan_cluster_node::scan_cluster_node_uuid::${scan_cluster_node_uuid}::scan_cluster_node_scan_cluster_uuid" => $anvil->data->{sql}{scan_cluster_node}{scan_cluster_node_uuid}{$scan_cluster_node_uuid}{scan_cluster_node_scan_cluster_uuid},
			"sql::scan_cluster_node::scan_cluster_node_uuid::${scan_cluster_node_uuid}::scan_cluster_node_host_uuid"         => $anvil->data->{sql}{scan_cluster_node}{scan_cluster_node_uuid}{$scan_cluster_node_uuid}{scan_cluster_node_host_uuid},
			"sql::scan_cluster_node::scan_cluster_node_uuid::${scan_cluster_node_uuid}::scan_cluster_node_name"              => $anvil->data->{sql}{scan_cluster_node}{scan_cluster_node_uuid}{$scan_cluster_node_uuid}{scan_cluster_node_name},
			"sql::scan_cluster_node::scan_cluster_node_uuid::${scan_cluster_node_uuid}::scan_cluster_node_pacemaker_id"      => $anvil->data->{sql}{scan_cluster_node}{scan_cluster_node_uuid}{$scan_cluster_node_uuid}{scan_cluster_node_pacemaker_id},
			"sql::scan_cluster_node::scan_cluster_node_uuid::${scan_cluster_node_uuid}::scan_cluster_node_in_ccm"            => $anvil->data->{sql}{scan_cluster_node}{scan_cluster_node_uuid}{$scan_cluster_node_uuid}{scan_cluster_node_in_ccm},
			"sql::scan_cluster_node::scan_cluster_node_uuid::${scan_cluster_node_uuid}::scan_cluster_node_crmd_member"       => $anvil->data->{sql}{scan_cluster_node}{scan_cluster_node_uuid}{$scan_cluster_node_uuid}{scan_cluster_node_crmd_member}, 
			"sql::scan_cluster_node::scan_cluster_node_uuid::${scan_cluster_node_uuid}::scan_cluster_node_cluster_member"    => $anvil->data->{sql}{scan_cluster_node}{scan_cluster_node_uuid}{$scan_cluster_node_uuid}{scan_cluster_node_cluster_member}, 
			"sql::scan_cluster_node::scan_cluster_node_uuid::${scan_cluster_node_uuid}::scan_cluster_node_maintenance_mode"  => $anvil->data->{sql}{scan_cluster_node}{scan_cluster_node_uuid}{$scan_cluster_node_uuid}{scan_cluster_node_maintenance_mode}, 
		}});
		
		$anvil->data->{sql}{scan_cluster_node_host_uuid}{$scan_cluster_node_host_uuid} = $scan_cluster_node_uuid;
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
			"sql::scan_cluster_node_host_uuid::${scan_cluster_node_host_uuid}" => $anvil->data->{sql}{scan_cluster_node_host_uuid}{$scan_cluster_node_host_uuid},
		}});
	}
	
	return(0);
}

# This reads in all the data we can find on the local system
sub collect_data
{
	my ($anvil) = @_;
	
	# Pick out core cluster details.
	my $problem = $anvil->Cluster->parse_cib({debug => 2});
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { problem => $problem }});
	
	# If there was a problem, we're not in the cluster.
	if ($problem)
	{
		my $changed = $anvil->Alert->check_alert_sent({
			record_locator => "scan_cluster::in_cluster",
			set_by         => $THIS_FILE,
		});
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { changed => $changed }});
		if ($changed)
		{
			# Register an alert.
			my $variables = { host_name => $anvil->Get->host_name() };
			$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "scan_cluster_alert_0010", variables => $variables});
			$anvil->Alert->register({alert_level => "warning", message => "scan_cluster_alert_0010", variables => $variables, set_by => $THIS_FILE});
		
			# See if I need to mark us as out of the cluster. Normally, our peer would do this, 
			# but if we went down at the same time as our peer, both of us might not update the 
			# membership values.
			my $query = "
SELECT 
    scan_cluster_node_uuid, 
    scan_cluster_node_in_ccm, 
    scan_cluster_node_crmd_member, 
    scan_cluster_node_cluster_member 
FROM 
    scan_cluster_nodes 
WHERE 
    scan_cluster_node_host_uuid = ".$anvil->Database->quote($anvil->Get->host_uuid)."
;";
			$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})
			{
				# We've got an entry in the 'scan_cluster_nodes' table, so now we'll look for data in the node and 
				# services tables.
				my $scan_cluster_node_uuid           = $row->[0]; 
				my $scan_cluster_node_in_ccm         = $row->[1]; 
				my $scan_cluster_node_crmd_member    = $row->[2]; 
				my $scan_cluster_node_cluster_member = $row->[3]; 
				$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
					scan_cluster_node_uuid           => $scan_cluster_node_uuid,
					scan_cluster_node_in_ccm         => $scan_cluster_node_in_ccm,
					scan_cluster_node_crmd_member    => $scan_cluster_node_crmd_member, 
					scan_cluster_node_cluster_member => $scan_cluster_node_cluster_member, 
				}});
				
				if (($scan_cluster_node_in_ccm) or ($scan_cluster_node_crmd_member) or ($scan_cluster_node_cluster_member))
				{
					# Update
					my $query = "
UPDATE 
    scan_cluster_nodes 
SET 
    scan_cluster_node_in_ccm         = '0', 
    scan_cluster_node_crmd_member    = '0', 
    scan_cluster_node_cluster_member = '0',
    modified_date                    = ".$anvil->Database->quote($anvil->Database->refresh_timestamp)."
WHERE 
    scan_cluster_node_uuid           = ".$anvil->Database->quote($scan_cluster_node_uuid)."
;";
					$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }});
					$anvil->Database->write({query => $query, source => $THIS_FILE, line => __LINE__});
				}
			}
		}
		
		# Exit now.
		$anvil->nice_exit({exit_code => 2});
	}
	else
	{
		# See if we came back into the cluster
		my $changed = $anvil->Alert->check_alert_sent({
			clear          => 1,
			record_locator => "scan_cluster::in_cluster",
			set_by         => $THIS_FILE,
		});
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { changed => $changed }});
		if ($changed)
		{
			# Register an alert.
			my $variables = { host_name => $anvil->Get->host_name() };
			$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "scan_cluster_alert_0011", variables => $variables});
			$anvil->Alert->register({alert_level => "warning", clear_alert => 1, message => "scan_cluster_alert_0011", variables => $variables, set_by => $THIS_FILE});
		}
	}
	
	return(0);
}
