# MonArch - Groundwork Monitor Architect
# MonarchFoundationSync.pm
#
############################################################################
# Release 4.0
# October 2011
############################################################################
#
# Copyright 2007-2011 GroundWork Open Source, Inc. (GroundWork)
# All rights reserved. This program is free software; you can redistribute
# it and/or modify it under the terms of the GNU General Public License
# version 2 as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
use strict;
package FoundationSync;
use IO::Socket;
use Carp;
use Time::HiRes;
use DBI;
use MonarchStorProc;
use MonarchAudit;
use CollageQuery;
my $use_sync_start_action = 1; # Leave as 1 to get Foundation to flush its internal queues of results already received.
my $use_sync_stop_action = 0; # Leave as 0.
my $foundation_msg_count = 0;
my $remote_host = "127.0.0.1";
my $remote_port = 4913;
my $logfile = "/usr/local/groundwork/foundation/container/logs/monarch_foundation_sync.log";
my $maxhostsendcount = 256; # a power of 2 makes division faster
my $max_bulk_host_add = 200;
my $single_obj_timeout = 0.350; # Estimated max seconds per object deletion or insertion.
my $wait_sleep_seconds = 3; # May be set to a fractional value.
my $abort_on_error = 1; # If set, abort Commit after first error seen.
# FIX THIS: set these values as needed for production use
my $show_timeouts = 1; # Set to 0 to suppress, 1 to show timeout messages.
my $show_napping = 1; # Set to 0 for normal operation, 1 to emit napping messages to Apache error log.
# (Set to 1 for now to guarantee Apache does not give up on a long-running commit operation.)
my $logging = 1; # Set to 0 to disable debug logging
# and to 1 for debug instance logging (a static file is overwritten)
# and to 2 for persistent logging (file will grow).
my $log_as_utf8 = 0; # Set to 0 to log Foundation messages as ISO-8859-1, to 1 to log as UTF-8.
my $debug = 0;
my $debug_waits = $debug >= 1;
my $errorstring = undef;
my $socket = undef;
my $collage_dbh = undef;
my $change_time = undef;
# FIX THIS: RaiseError, PrintError? what about the same in the event feeder?
sub collage_connect {
my ( $dbname, $dbhost, $dbuser, $dbpass, $dbtype ) = CollageQuery::readGroundworkDBConfig('collage');
my $dsn = '';
if ( defined($dbtype) && $dbtype eq 'postgresql' ) {
$dsn = "DBI:Pg:dbname=$dbname;host=$dbhost";
}
else {
$dsn = "DBI:mysql:database=$dbname;host=$dbhost";
}
$collage_dbh = DBI->connect( $dsn, $dbuser, $dbpass, { 'AutoCommit' => 1, 'RaiseError' => 1 } )
or return "Can't connect to database $dbname. Error:" . $DBI::errstr;
return '';
}
sub collage_disconnect {
$collage_dbh->disconnect();
}
sub count_objects_statement {
my $obj_type = $_[0];
if ($obj_type eq 'hosts') {
return "select count(*) from ApplicationType at, Host h where at.Name = 'NAGIOS' and h.ApplicationTypeID = at.ApplicationTypeID";
}
elsif ($obj_type eq 'services') {
return "select count(*) from ApplicationType at, ServiceStatus ss where at.Name = 'NAGIOS' and ss.ApplicationTypeID = at.ApplicationTypeID";
}
elsif ($obj_type eq 'hostgroups') {
# Someday, when we count hostgroups in MonarchAudit using a CollageQuery query that qualifies
# by ApplicationType being NAGIOS, we will want to use this more complex query here.
# return "select count(*) from ApplicationType at, HostGroup hg where at.Name = 'NAGIOS' and hg.ApplicationTypeID = at.ApplicationTypeID";
return "select count(*) from HostGroup";
}
elsif ($obj_type eq 'hostgroup members') {
# Someday, when we count hostgroups and hostgroup members in MonarchAudit using CollageQuery
# queries that qualify by ApplicationType being NAGIOS, we will want to use a more complex
# query here, similar to the following except that it should also match the hostgroup to the
# ApplicationType NAGIOS, not just the individual hosts.
# return "select count(*) from ApplicationType at, Host h, HostGroupCollection hgc where at.Name = 'NAGIOS' and h.ApplicationTypeID = at.ApplicationTypeID and hgc.HostID = h.HostID";
return "select count(*) from HostGroupCollection";
}
elsif ($obj_type eq 'servicegroups') {
# Someday, when we count servicegroups in MonarchAudit using a CollageQuery query that
# qualifies by ApplicationType being NAGIOS, we will want to use a more complex query
# here that also imposes such a qualification.
return "select count(*) from Category c, EntityType et where et.Name = 'SERVICE_GROUP' and c.EntityTypeID = et.EntityTypeID";
}
elsif ($obj_type eq 'servicegroup members') {
# Someday, when we count servicegroups and servicegroup members in MonarchAudit using
# CollageQuery queries that qualify by ApplicationType being NAGIOS, we will want to
# use a more complex query here that also imposes such qualifications.
return
"select count(*)
from ServiceStatus ss, CategoryEntity ce, Category c, EntityType et
where et.Name = 'SERVICE_GROUP'
and c.EntityTypeID = et.EntityTypeID
and ce.CategoryID = c.CategoryID
and ss.ServiceStatusID = ce.ObjectID";
}
else {
return undef;
}
}
sub wait_for_foundation {
# my $self = $_[0];
my $change = $_[1];
my $obj_type = $_[2];
my $obj_delta = $_[3];
my $obj_count = $_[4];
my $starttime = Time::HiRes::time();
my $timeout = $obj_delta * $single_obj_timeout; # Seconds to wait before issuing a warning.
my @warnings = ();
my @errors = ();
my $now;
my $timeleft;
# FIX THIS: This is just a temporary override, to guarantee we won't run into timeouts during development,
# until we all have the things that might time out optimized so they shouldn't ever.
# $timeout = 900;
print STDERR "=== Waiting for $change of $obj_delta $obj_type, to end up with $obj_count.\n" if $debug_waits;
# FIX THIS?
# return \@errors if ($obj_delta == 0);
my $sqlstmt = count_objects_statement($obj_type);
if (!$sqlstmt) {
push @errors, "Error: bad object type '$obj_type'";
}
else {
my $count;
my $prev_count = -1;
my $napped = 0;
for (;;) {
last if $main::shutdown_requested;
$count = $collage_dbh->selectrow_array($sqlstmt);
if ( !defined($count) ) {
print STDERR "Error: cannot count $obj_type: ", $collage_dbh->errstr, "\n";
push @errors, "Error: cannot count $obj_type: ", $collage_dbh->errstr;
$napped = 0;
last;
}
last if ($count == $obj_count);
if ($prev_count >= 0 &&
( ( $change eq 'add' && $count < $prev_count ) || ( ( $change eq 'delete' || $change eq 'clear' ) && $count > $prev_count ) )
)
{
# FIX LATER: when we get back to aborting when timeouts occur, this should be an error, not a warning
print STDERR "Warning: In $change of $obj_delta $obj_type, object count is "
. ($change eq 'add' ? 'decreasing' : 'increasing') . " (now have $count).\n";
push @warnings, "Warning: Count of $obj_type is going in the wrong direction for $change.";
last;
}
$prev_count = $count;
# To accommodate very small changes without spurious errors, we don't
# enforce a timeout until we have waited at least one napping cycle.
$now = Time::HiRes::time();
if ( $napped && ($now - $starttime) > $timeout ) {
if ( $show_timeouts ) {
push @warnings, "Notice: Expecting $obj_count, have $count $obj_type;";
push @warnings, "$change of $obj_delta $obj_type is taking more than estimated $timeout seconds.";
}
# FIX THIS: someday, when we no longer warn but only issue errors on timeouts, we can put these lines back.
# print STDERR ">>> Have $count $obj_type; Foundation is taking too long; Commit will be aborted!\n" if ($show_napping || $debug_waits);
# push @errors, "Error: Foundation is taking too long to process changes; Commit has been aborted!";
print STDERR ">>> Have $count $obj_type; Foundation is taking too long; Commit will continue without a full wait.\n" if ($show_napping || $debug_waits);
push @warnings, "Warning: Foundation is taking too long to process changes; Commit will continue without a full wait.";
last;
}
$timeleft = $starttime + $timeout - $now;
$timeleft = 0 if $timeleft < 0;
$timeleft = sprintf('%0.3f', $timeleft);
print STDERR "napping for $change of $obj_delta $obj_type; want $obj_count, have $count so far; $timeleft secs left\n" if $show_napping;
select undef, undef, undef, $wait_sleep_seconds;
$napped = 1;
}
if ($napped && $show_napping && $count == $obj_count) {
$now = Time::HiRes::time();
my $waittime = $now - $starttime;
$waittime = sprintf('%0.3f', $waittime);
print STDERR "napping for $change of $obj_delta $obj_type is over; waiting took $waittime secs\n";
}
}
return \@warnings, \@errors;
}
# sync() - Synchronize the monarch and foundation databases for a single Monarch group
#
sub sync_group {
# my $self = $_[0];
my $group = $_[1];
sync('',{'group'=>$group});
}
# sync() - Synchronize the monarch and foundation databases.
# This comment is old and now merely suggestive.
# Stop event broker (actually, all of nagios, including the event feeder, outside of this routine).
# Call Audit->foundation_sync() to build data structure containing
# delta between previous state and current state, based on
# difference between monarch and foundation databases.
# Preserve current state of modified objects with calls to CollageQuery methods. (???)
# Prepare commands in XML format for consumption by Foundation, and
# send them to a socket on which Foundation is listening.
# Restart event broker (all of nagios, including the event feeder, outside of this routine).
sub sync($$) {
# my $self = $_[0];
my $arg_ref = $_[1];
my @warnings = ();
my @errors = ();
my @timing = ();
my $warn_ref = undef;
my $err_ref = undef;
my $time_ref = undef;
my %delta = ();
my $phasetime;
StorProc->start_timing( \$phasetime );
# FIX MINOR: it would probably be more efficient to pass back a hashref, rather than copy the whole big %delta structure
if (defined($arg_ref) && defined($arg_ref->{'group'})) {
($err_ref, $time_ref, %delta) = Audit->foundation_sync_group($arg_ref->{'group'});
}
else {
($err_ref, $time_ref, %delta) = Audit->foundation_sync();
}
push @errors, @$err_ref;
push @timing, @$time_ref;
StorProc->capture_timing( \@timing, \$phasetime, 'running Audit' );
if (@errors) {
$errorstring = join( '
', @errors );
return \@timing, $errorstring;
}
# snapshot from monarch -- check this before auto-vivification below changes the picture
unless ($delta{'add'} || $delta{'delete'} || $delta{'alter'}) {
return \@timing, "Synchronization with Foundation completed successfully. No changes were needed.";
}
my $deleted_hosts = scalar keys %{ $delta{'delete'}{'host'} };
my $deleted_hostgroups = scalar keys %{ $delta{'delete'}{'hostgroup'} };
my $deleted_services = 0;
my $deleted_servicegroups = scalar keys %{ $delta{'delete'}{'servicegroup'} };
foreach my $host (keys %{ $delta{'delete'}{'service'} }) {
$deleted_services += scalar keys %{ $delta{'delete'}{'service'}{$host} };
}
# We need to count as well the objects that are not individually deleted but will be automatically
# cascade-deleted when containing objects are deleted, so the final counts we wait for will be correct.
my $cascade_deleted_services = $delta{'statistics'}{'cascade_deleted_services'};
my $cascade_deleted_hostgroup_members = $delta{'statistics'}{'cascade_deleted_hostgroup_members'};
my $cascade_deleted_servicegroup_members = $delta{'statistics'}{'cascade_deleted_servicegroup_members'};
my $added_hosts = scalar keys %{ $delta{'add'}{'host'} };
my $added_hostgroups = scalar keys %{ $delta{'add'}{'hostgroup'} };
my $added_services = 0;
my $added_servicegroups = scalar keys %{ $delta{'add'}{'servicegroup'} };
foreach my $host (keys %{ $delta{'add'}{'service'} }) {
$added_services += scalar keys %{ $delta{'add'}{'service'}{$host} };
}
# FIX THIS: calculate the proper values to assign here, and do so (I believe this is correct, but do some testing)
my $cleared_hostgroup_members = $delta{'statistics'}{'cleared_hostgroup_members'};
my $cleared_servicegroup_members = $delta{'statistics'}{'cleared_servicegroup_members'};
my $status = collage_connect();
# FIX THIS
push @errors, $status if $status;
print STDERR $status, "\n" if $status;
if (@errors) {
$errorstring = join( '
', @errors );
return \@timing, $errorstring;
}
# Initial numbers of objects in Foundation, to which deletes and adds will be applied.
# FIX THIS: should these numbers be derived from these hashes instead?
# %delta{'exists'}{'hostgroup'}
# %delta{'exists'}{'service'}
# %delta{'exists'}{'host'}
# %delta{'exists'}{} # maybe 'servicegroup' ? need to prove it first
my $initial_host_count = $collage_dbh->selectrow_array( count_objects_statement('hosts') );
my $initial_hostgroup_count = $collage_dbh->selectrow_array( count_objects_statement('hostgroups') );
my $initial_service_count = $collage_dbh->selectrow_array( count_objects_statement('services') );
my $initial_servicegroup_count = $collage_dbh->selectrow_array( count_objects_statement('servicegroups') );
my $initial_hostgroup_member_count = $collage_dbh->selectrow_array( count_objects_statement('hostgroup members') );
my $initial_servicegroup_member_count = $collage_dbh->selectrow_array( count_objects_statement('servicegroup members') );
# The $low_service_count value here corresponds to after deleting individual services but not yet
# taking account of additional services that will be cascade-deleted when deleting entire hosts.
my $low_host_count = $initial_host_count - $deleted_hosts;
my $low_hostgroup_count = $initial_hostgroup_count - $deleted_hostgroups;
my $low_service_count = $initial_service_count - $deleted_services;
my $low_servicegroup_count = $initial_servicegroup_count - $deleted_servicegroups;
# The $high_service_count value here must take into account all services that were cascade-deleted.
my $high_host_count = $low_host_count + $added_hosts;
my $high_hostgroup_count = $low_hostgroup_count + $added_hostgroups;
my $high_service_count = $low_service_count - $cascade_deleted_services + $added_services;
my $high_servicegroup_count = $low_servicegroup_count + $added_servicegroups;
# FIX THIS
my $low_hostgroup_member_count = $initial_hostgroup_member_count - $cascade_deleted_hostgroup_members - $cleared_hostgroup_members;
my $low_servicegroup_member_count = $initial_servicegroup_member_count - $cascade_deleted_servicegroup_members - $cleared_servicegroup_members;
my $logfh;
if ($logging == 1) {
open( $logfh, '>', $logfile) && $logfh->autoflush(1);
$errorstring = "$!: $logfile Foundation sync is in debug mode but cannot write to log." if $! =~ /permission denied/i;
}
elsif ($logging == 2) {
open( $logfh, '>>', $logfile) && $logfh->autoflush(1);
$errorstring = "$!: $logfile Foundation sync is in debug mode but cannot append to log." if $! =~ /permission denied/i;
}
# Connection
my $max_connect_attempts = 3;
unless ($errorstring) {
my $status;
for (my $i = 0; $i <= $max_connect_attempts; $i++) {
if ($i == $max_connect_attempts) {
print $logfh "Couldn't connect to $remote_host:$remote_port : $status\n" if $logging;
$errorstring = "Error: Unable to connect to Collage database (check gwservices).\n";
} else {
$socket = IO::Socket::INET->new( PeerAddr => $remote_host, PeerPort => $remote_port, Proto => "tcp", Type => SOCK_STREAM );
if ($socket) {
# FIX THIS: this is perhaps also something we want to send to $socket;
# there's no point in logging it here unless we do so
# print $logfh "\n" if $logging;
# Ensure that we push out all the data we write, before
# we go waiting for Foundation to finish processing it.
$socket->autoflush(1);
last;
} else {
$status = $!;
sleep 1;
}
}
}
}
if ($logging) {
printf $logfh "hosts: %5d - %5d = %5d; + %5d = %5d\n",
$initial_host_count, $deleted_hosts, $low_host_count, $added_hosts, $high_host_count;
printf $logfh "hostgroups: %5d - %5d = %5d; + %5d = %5d\n",
$initial_hostgroup_count, $deleted_hostgroups, $low_hostgroup_count, $added_hostgroups, $high_hostgroup_count;
printf $logfh "services: %5d - %5d = %5d; - %5d + %5d = %5d\n",
$initial_service_count, $deleted_services, $low_service_count, $cascade_deleted_services, $added_services, $high_service_count;
printf $logfh "servicegroups: %5d - %5d = %5d; + %5d = %5d\n",
$initial_servicegroup_count, $deleted_servicegroups, $low_servicegroup_count, $added_servicegroups, $high_servicegroup_count;
printf $logfh "hostgroup members: %5d - %5d - %5d = %5d; + ????? = ?????\n",
$initial_hostgroup_member_count, $cascade_deleted_hostgroup_members, $cleared_hostgroup_members, $low_hostgroup_member_count;
printf $logfh "servicegroup members: %5d - %5d - %5d = %5d; + ????? = ?????\n",
$initial_servicegroup_member_count, $cascade_deleted_servicegroup_members, $cleared_servicegroup_members, $low_servicegroup_member_count;
}
StorProc->capture_timing( \@timing, \$phasetime, 'data preparation and Foundation connection' );
if ($socket) {
my $xml_out = '';
## print $logfh "Begin process ...\n" if $logging;
if ($use_sync_start_action) {
$xml_out = "\n";
print $socket $xml_out;
print $logfh $xml_out if $logging;
$xml_out = '';
}
# push all changes to Foundation, in measured stages
# We only call this once, because we expect that time formatting is an expensive operation when done
# repeatedly, and we don't need any finer resolution than the moment at which the entire sync is run.
$change_time = get_last_state_change();
# As a general rule, we need to to wait for deletions to finish before starting the corresponding
# additions, bacause we cannot tell if the additions are truly done unless we are truly sure that
# all the deletions are also done. (Otherwise, we might still have some offsetting delete/add
# pairs still outstanding, leading to a matching count when the work is not yet done.)
unless ($main::shutdown_requested || (@errors && $abort_on_error)) {
delete_services( \%delta, $socket, $logfh );
($warn_ref, $err_ref) = wait_for_foundation( '', 'delete', 'services', $deleted_services, $low_service_count );
push @warnings, @$warn_ref;
push @errors, @$err_ref;
StorProc->capture_timing( \@timing, \$phasetime, "delete of $deleted_services services" );
}
unless ($main::shutdown_requested || (@errors && $abort_on_error)) {
delete_hosts( \%delta, $socket, $logfh );
($warn_ref, $err_ref) = wait_for_foundation( '', 'delete', 'hosts', $deleted_hosts, $low_host_count );
push @warnings, @$warn_ref;
push @errors, @$err_ref;
StorProc->capture_timing( \@timing, \$phasetime, "delete of $deleted_hosts hosts" );
}
unless ($main::shutdown_requested || (@errors && $abort_on_error)) {
add_hosts( \%delta, $socket, $logfh );
($warn_ref, $err_ref) = wait_for_foundation( '', 'add', 'hosts', $added_hosts, $high_host_count );
push @warnings, @$warn_ref;
push @errors, @$err_ref;
StorProc->capture_timing( \@timing, \$phasetime, "add of $added_hosts hosts" );
}
unless ($main::shutdown_requested || (@errors && $abort_on_error)) {
add_services( \%delta, $socket, $logfh );
($warn_ref, $err_ref) = wait_for_foundation( '', 'add', 'services', $added_services, $high_service_count );
push @warnings, @$warn_ref;
push @errors, @$err_ref;
StorProc->capture_timing( \@timing, \$phasetime, "add of $added_services services" );
}
unless ($main::shutdown_requested || (@errors && $abort_on_error)) {
delete_hostgroups ( \%delta, $socket, $logfh );
delete_servicegroups( \%delta, $socket, $logfh );
($warn_ref, $err_ref) = wait_for_foundation( '', 'delete', 'hostgroups', $deleted_hostgroups, $low_hostgroup_count );
push @warnings, @$warn_ref;
push @errors, @$err_ref;
StorProc->capture_timing( \@timing, \$phasetime, "delete of $deleted_hostgroups hostgroups" );
($warn_ref, $err_ref) = wait_for_foundation( '', 'delete', 'servicegroups', $deleted_servicegroups, $low_servicegroup_count );
push @warnings, @$warn_ref;
push @errors, @$err_ref;
StorProc->capture_timing( \@timing, \$phasetime, "delete of $deleted_servicegroups servicegroups" );
}
unless ($main::shutdown_requested || (@errors && $abort_on_error)) {
add_hostgroups ( \%delta, $socket, $logfh );
add_servicegroups( \%delta, $socket, $logfh );
($warn_ref, $err_ref) = wait_for_foundation( '', 'add', 'hostgroups', $added_hostgroups, $high_hostgroup_count );
push @warnings, @$warn_ref;
push @errors, @$err_ref;
StorProc->capture_timing( \@timing, \$phasetime, "add of $added_hostgroups hostgroups" );
($warn_ref, $err_ref) = wait_for_foundation( '', 'add', 'servicegroups', $added_servicegroups, $high_servicegroup_count );
push @warnings, @$warn_ref;
push @errors, @$err_ref;
StorProc->capture_timing( \@timing, \$phasetime, "add of $added_servicegroups servicegroups" );
}
unless ($main::shutdown_requested || (@errors && $abort_on_error)) {
add_hostgroup_members ( \%delta, $socket, $logfh );
add_servicegroup_members( \%delta, $socket, $logfh );
update_hostgroups ( \%delta, $socket, $logfh );
update_servicegroups ( \%delta, $socket, $logfh );
update_hosts ( \%delta, $socket, $logfh );
update_services ( \%delta, $socket, $logfh );
}
if ($use_sync_stop_action) {
$xml_out = "\n";
}
$xml_out .= "\n";
print $socket $xml_out;
print $logfh $xml_out if $logging;
$xml_out = '';
print $socket $xml_out;
print $logfh "$xml_out\n\n" if $logging;
close $socket;
}
collage_disconnect();
close $logfh if $logging;
StorProc->capture_timing( \@timing, \$phasetime, "sending of final hostgroup, servicegroup, host, and service changes" );
if ($main::shutdown_requested) {
push @errors, 'Error: Processing was interrupted.';
}
if (@errors) {
# FIX THIS: I'd like a cleaner way to hand back all the error strings.
# Also, the warnings and errors won't necessarily appear here interleaved in the order they originally occurred.
$errorstring = join( '
', @warnings, @errors );
return \@timing, $errorstring;
} else {
$errorstring = join( '
', @warnings, "Synchronization with Foundation completed successfully. Changes were required." );
return \@timing, $errorstring;
}
}
sub delete_hosts {
my $delta = $_[0];
my $socket = $_[1];
my $logfh = $_[2];
my $cnt = 0;
my @xmlstring = ();
foreach my $host (keys %{ $delta->{'delete'}{'host'} }) {
push @xmlstring, "\t\n";
if (++$cnt == $max_bulk_host_add) {
write_command_xml( $socket, $logfh, "REMOVE", join( '', @xmlstring ) );
@xmlstring = ();
$cnt = 0;
}
}
write_command_xml( $socket, $logfh, "REMOVE", join( '', @xmlstring ) ) if (@xmlstring);
}
sub delete_services {
my $delta = $_[0];
my $socket = $_[1];
my $logfh = $_[2];
my $cnt = 0;
my @xmlstring = ();
foreach my $host (keys %{ $delta->{'delete'}{'service'} }) {
foreach my $service (keys %{ $delta->{'delete'}{'service'}{$host} }) {
push @xmlstring, "\t\n";
if (++$cnt == $max_bulk_host_add) {
write_command_xml( $socket, $logfh, "REMOVE", join( '', @xmlstring ) );
@xmlstring = ();
$cnt = 0;
}
}
}
write_command_xml( $socket, $logfh, "REMOVE", join( '', @xmlstring ) ) if (@xmlstring);
}
sub add_hosts {
my $delta = $_[0];
my $socket = $_[1];
my $logfh = $_[2];
my $cnt = 0;
my @xmlstring = ();
my $alias;
my $notes;
foreach my $host (keys %{ $delta->{'add'}{'host'} }) {
$alias = $delta->{'add'}{'host'}{$host}{'alias'};
$notes = $delta->{'add'}{'host'}{$host}{'notes'};
# FIX LATER: Is the existence of an alias guaranteed here? Yes, supposedly.
$alias =~ s/\n/ /g;
$alias =~ s/
/ /ig;
$alias =~ s/&/&/g;
$alias =~ s/"/"/g;
$alias =~ s/'/'/g;
$alias =~ s/</g;
$alias =~ s/>/>/g;
if (defined $notes) {
$notes =~ s/&/&/g;
$notes =~ s/"/"/g;
$notes =~ s/'/'/g;
$notes =~ s/</g;
$notes =~ s/>/>/g;
}
push @xmlstring,
"\t\n";
if (++$cnt == $max_bulk_host_add) {
write_command_xml( $socket, $logfh, "ADD", join( '', @xmlstring ) );
@xmlstring = ();
$cnt = 0;
}
}
write_command_xml( $socket, $logfh, "ADD", join( '', @xmlstring ) ) if (@xmlstring);
}
# Add new services
sub add_services {
my $delta = $_[0];
my $socket = $_[1];
my $logfh = $_[2];
my $cnt = 0;
my @xmlstring = ();
my $notes;
foreach my $host (keys %{ $delta->{'add'}{'service'} }) {
foreach my $service (keys %{ $delta->{'add'}{'service'}{$host} }) {
$notes = $delta->{'add'}{'servicenotes'}{$host}{$service};
if (defined $notes) {
$notes =~ s/&/&/g;
$notes =~ s/"/"/g;
$notes =~ s/'/'/g;
$notes =~ s/</g;
$notes =~ s/>/>/g;
}
push @xmlstring,
"\t\n";
if (++$cnt == $max_bulk_host_add) {
write_command_xml( $socket, $logfh, "ADD", join( '', @xmlstring ) );
@xmlstring = ();
$cnt = 0;
}
}
}
write_command_xml( $socket, $logfh, "ADD", join( '', @xmlstring ) ) if (@xmlstring);
}
sub update_hosts {
my $delta = $_[0];
my $socket = $_[1];
my $logfh = $_[2];
my $cnt = 0;
my @xmlstring = ();
my $alias;
my $address;
my $notes;
my $parents;
foreach my $host (keys %{ $delta->{'alter'}{'host'} }) {
$alias = $delta->{'alter'}{'host'}{$host}{'alias'};
$address = $delta->{'alter'}{'host'}{$host}{'address'};
$notes = $delta->{'alter'}{'host'}{$host}{'notes'};
$parents = $delta->{'alter'}{'host'}{$host}{'parents'};
push @xmlstring, "\t/ /ig;
$alias =~ s/&/&/g;
$alias =~ s/"/"/g;
$alias =~ s/'/'/g;
$alias =~ s/</g;
$alias =~ s/>/>/g;
push @xmlstring, " Alias='", $alias, "'";
}
if (defined $address) {
push @xmlstring, " Device='", $address , "'";
}
if (defined $notes) {
$notes =~ s/&/&/g;
$notes =~ s/"/"/g;
$notes =~ s/'/'/g;
$notes =~ s/</g;
$notes =~ s/>/>/g;
push @xmlstring, " Notes='", $notes, "'";
}
if (defined $parents) {
push @xmlstring, " Parent='", $parents, "'";
}
push @xmlstring, " />\n";
if (++$cnt == $max_bulk_host_add) {
write_command_xml( $socket, $logfh, "MODIFY", join( '', @xmlstring ) );
@xmlstring = ();
$cnt = 0;
}
}
write_command_xml( $socket, $logfh, "MODIFY", join( '', @xmlstring ) ) if (@xmlstring);
}
# Change existing service properties
sub update_services {
my $delta = $_[0];
my $socket = $_[1];
my $logfh = $_[2];
my $cnt = 0;
my @xmlstring = ();
my $notes;
foreach my $host (keys %{ $delta->{'alter'}{'service'} }) {
foreach my $service (keys %{ $delta->{'alter'}{'service'}{$host} }) {
# No {'notes'} appended, for now. See the audit routine for this construction.
$notes = $delta->{'alter'}{'service'}{$host}{$service};
if (defined $notes) {
$notes =~ s/&/&/g;
$notes =~ s/"/"/g;
$notes =~ s/'/'/g;
$notes =~ s/</g;
$notes =~ s/>/>/g;
}
push @xmlstring,
"\t\n";
if (++$cnt == $max_bulk_host_add) {
write_command_xml( $socket, $logfh, "MODIFY", join( '', @xmlstring ) );
@xmlstring = ();
$cnt = 0;
}
}
}
write_command_xml( $socket, $logfh, "MODIFY", join( '', @xmlstring ) ) if (@xmlstring);
}
sub delete_hostgroups {
my $delta = $_[0];
my $socket = $_[1];
my $logfh = $_[2];
my $cnt = 0;
my @xmlstring = ();
foreach my $hostgroup (keys %{ $delta->{'delete'}{'hostgroup'} }) {
push @xmlstring, "\t\n";
if (++$cnt == $max_bulk_host_add) {
write_command_xml( $socket, $logfh, "REMOVE", join( '', @xmlstring ) );
@xmlstring = ();
$cnt = 0;
}
}
write_command_xml( $socket, $logfh, "REMOVE", join( '', @xmlstring ) ) if (@xmlstring)
}
sub delete_servicegroups {
my $delta = $_[0];
my $socket = $_[1];
my $logfh = $_[2];
my $cnt = 0;
my @xmlstring = ();
foreach my $servicegroup (keys %{ $delta->{'delete'}{'servicegroup'} }) {
push @xmlstring, "\t\n";
if (++$cnt == $max_bulk_host_add) {
write_command_xml( $socket, $logfh, "REMOVE", join( '', @xmlstring ) );
@xmlstring = ();
$cnt = 0;
}
}
write_command_xml( $socket, $logfh, "REMOVE", join( '', @xmlstring ) ) if (@xmlstring);
}
sub add_hostgroups {
my $delta = $_[0];
my $socket = $_[1];
my $logfh = $_[2];
my $cnt = 0;
my @xmlstring = ();
my $alias;
my $notes;
foreach my $hostgroup (keys %{ $delta->{'add'}{'hostgroup'} }) {
## build host group in chunks
$alias = $delta->{'add'}{'hostgroup'}{$hostgroup}{'alias'};
$notes = $delta->{'add'}{'hostgroup'}{$hostgroup}{'notes'};
if (defined $alias) {
$alias =~ s/\n/ /g;
$alias =~ s/
/ /ig;
$alias =~ s/&/&/g;
$alias =~ s/"/"/g;
$alias =~ s/'/'/g;
$alias =~ s/</g;
$alias =~ s/>/>/g;
}
if (defined $notes) {
$notes =~ s/&/&/g;
$notes =~ s/"/"/g;
$notes =~ s/'/'/g;
$notes =~ s/</g;
$notes =~ s/>/>/g;
}
push @xmlstring,
"\t\n";
if (++$cnt == $max_bulk_host_add) {
write_command_xml( $socket, $logfh, "ADD", join( '', @xmlstring ) );
@xmlstring = ();
$cnt = 0;
}
}
write_command_xml( $socket, $logfh, "ADD", join( '', @xmlstring ) ) if (@xmlstring);
}
sub add_servicegroups {
my $delta = $_[0];
my $socket = $_[1];
my $logfh = $_[2];
my $cnt = 0;
my @xmlstring = ();
my $notes;
foreach my $servicegroup (keys %{ $delta->{'add'}{'servicegroup'} }) {
## build service group in chunks
$notes = $delta->{'add'}{'servicegroup'}{$servicegroup}{'notes'};
if (defined $notes) {
$notes =~ s/&/&/g;
$notes =~ s/"/"/g;
$notes =~ s/'/'/g;
$notes =~ s/</g;
$notes =~ s/>/>/g;
}
push @xmlstring,
"\t\n";
if (++$cnt == $max_bulk_host_add) {
write_command_xml( $socket, $logfh, "ADD", join( '', @xmlstring ) );
@xmlstring = ();
$cnt = 0;
}
}
write_command_xml( $socket, $logfh, "ADD", join( '', @xmlstring ) ) if (@xmlstring);
}
# FIX LATER: bundle the socket writes here
sub add_hostgroup_members {
my $delta = $_[0];
my $socket = $_[1];
my $logfh = $_[2];
foreach my $hostgroup (keys %{ $delta->{'add'}{'hostgroup'} }) {
my $total_keys = keys %{ $delta->{'add'}{'hostgroup'}{$hostgroup}{'members'} };
my $key_index = 0;
my @members = ();
foreach my $host (keys %{ $delta->{'add'}{'hostgroup'}{$hostgroup}{'members'} }) {
push @members, "\t\t\n";
if (++$key_index == $total_keys || ($key_index % $maxhostsendcount) == 0) {
my $xmlstring = join( '', "\t\n", @members, "\t\n" );
write_command_xml( $socket, $logfh, "MODIFY", $xmlstring );
@members = ();
}
}
}
}
# FIX LATER: bundle the socket writes here
sub add_servicegroup_members {
my $delta = $_[0];
my $socket = $_[1];
my $logfh = $_[2];
foreach my $servicegroup (keys %{ $delta->{'add'}{'servicegroup'} }) {
# Find the total number of host+services in this group
my $total_keys = 0;
foreach my $host (keys %{ $delta->{'add'}{'servicegroup'}{$servicegroup}{'members'} }) {
$total_keys += scalar keys %{ $delta->{'add'}{'servicegroup'}{$servicegroup}{'members'}{$host} };
}
my $key_index = 0;
my @members = ();
foreach my $host (keys %{ $delta->{'add'}{'servicegroup'}{$servicegroup}{'members'} }) {
foreach my $service (keys %{ $delta->{'add'}{'servicegroup'}{$servicegroup}{'members'}{$host} }) {
push @members, "\t\t\n";
if (++$key_index == $total_keys || ($key_index % $maxhostsendcount) == 0) {
my $xmlstring = join( '', "\t\n", @members, "\t\n" );
write_command_xml( $socket, $logfh, "MODIFY", $xmlstring );
@members = ();
}
}
}
}
}
# FIX LATER: perhaps bundle the socket writes here
sub update_hostgroups {
my $delta = $_[0];
my $socket = $_[1];
my $logfh = $_[2];
## rebuild host groups in chunks
foreach my $hostgroup (keys %{ $delta->{'alter'}{'hostgroup'} }) {
my $alias = $delta->{'alter'}{'hostgroup'}{$hostgroup}{'alias'};
my $notes = $delta->{'alter'}{'hostgroup'}{$hostgroup}{'notes'};
if (defined $alias) {
$alias =~ s/\n/ /g;
$alias =~ s/
/ /ig;
$alias =~ s/&/&/g;
$alias =~ s/"/"/g;
$alias =~ s/'/'/g;
$alias =~ s/</g;
$alias =~ s/>/>/g;
}
if (defined $notes) {
$notes =~ s/&/&/g;
$notes =~ s/"/"/g;
$notes =~ s/'/'/g;
$notes =~ s/</g;
$notes =~ s/>/>/g;
}
my @xmlstring = ();
if (exists $delta->{'alter'}{'hostgroup'}{$hostgroup}{'members'}) {
# CLEAR must be bundled in the same packet with MODIFY
# of membership for a given hostgroup, to ensure serialization.
my $xmlstring = "\t\n";
push @xmlstring, " \n$xmlstring \n";
my $total_keys = keys %{ $delta->{'alter'}{'hostgroup'}{$hostgroup}{'members'} };
my $key_index = 0;
my @members = ();
foreach my $host (keys %{ $delta->{'alter'}{'hostgroup'}{$hostgroup}{'members'} }) {
push @members,
"\t\t\n";
if (++$key_index == $total_keys || ($key_index % $maxhostsendcount) == 0) {
my $xmlstring = join( '',
"\t\n", @members, "\t\n" );
push @xmlstring, " \n$xmlstring \n";
$alias = undef;
$notes = undef;
@members = ();
}
}
}
if (defined $alias || defined $notes) {
my $xmlstring = join( '',
"\t\n", "\t\n" );
push @xmlstring, " \n$xmlstring \n";
}
write_adapter_xml( $socket, $logfh, join( '', @xmlstring ) ) if @xmlstring;
}
}
# FIX LATER: perhaps bundle the socket writes here
sub update_servicegroups {
my $delta = $_[0];
my $socket = $_[1];
my $logfh = $_[2];
# rebuild service groups in chunks
foreach my $servicegroup (keys %{ $delta->{'alter'}{'servicegroup'} }) {
my $notes = $delta->{'alter'}{'servicegroup'}{$servicegroup}{'notes'};
if (defined $notes) {
$notes =~ s/&/&/g;
$notes =~ s/"/"/g;
$notes =~ s/'/'/g;
$notes =~ s/</g;
$notes =~ s/>/>/g;
}
my @xmlstring = ();
if (exists $delta->{'alter'}{'servicegroup'}{$servicegroup}{'members'}) {
# CLEAR must be bundled in the same packet with MODIFY
# of membership for a given servicegroup, to ensure serialization.
my $xmlstring = "\t\n";
push @xmlstring, " \n$xmlstring \n";
## Find the total number of host+services
my $total_keys = 0;
foreach my $host (keys %{ $delta->{'alter'}{'servicegroup'}{$servicegroup}{'members'} }) {
$total_keys += scalar keys %{ $delta->{'alter'}{'servicegroup'}{$servicegroup}{'members'}{$host} };
}
my $key_index = 0;
my @members = ();
foreach my $host (keys %{ $delta->{'alter'}{'servicegroup'}{$servicegroup}{'members'} }) {
foreach my $service (keys %{ $delta->{'alter'}{'servicegroup'}{$servicegroup}{'members'}{$host} }) {
push @members,
"\t\t\n";
if (++$key_index == $total_keys || ($key_index % $maxhostsendcount) == 0) {
my $xmlstring = join( '',
"\t\n", @members, "\t\n" );
push @xmlstring, " \n$xmlstring \n";
$notes = undef;
@members = ();
}
}
}
}
if (defined $notes) {
my $xmlstring = join( '',
"\t\n", "\t\n" );
push @xmlstring, " \n$xmlstring \n";
}
write_adapter_xml( $socket, $logfh, join( '', @xmlstring ) ) if @xmlstring;
}
}
sub write_adapter_xml {
my $socket = $_[0];
my $log_fh = $_[1];
my $xml_string = $_[2];
$foundation_msg_count++;
my $xml_out = "\n$xml_string\n";
print $log_fh $xml_out if $logging && !$log_as_utf8;
utf8::encode($xml_out);
print $socket $xml_out;
print $log_fh $xml_out if $logging && $log_as_utf8;
}
sub write_command_xml {
my $socket = $_[0];
my $log_fh = $_[1];
my $action = $_[2];
my $xml_string = $_[3];
$foundation_msg_count++;
my $xml_out = "\n \n$xml_string \n\n";
print $log_fh $xml_out if $logging && !$log_as_utf8;
utf8::encode($xml_out);
print $socket $xml_out;
print $log_fh $xml_out if $logging && $log_as_utf8;
}
sub unindent {
$_[0] =~ s/^[\n\r]*//;
my ($indent) = ($_[0] =~ /^([ \t]+)/);
$_[0] =~ s/^$indent//gm;
}
# Subroutine for getting the current time in SQL Date format
sub get_last_state_change {
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
return sprintf("%04d-%02d-%02d %02d:%02d:%02d", $year+1900, $mon+1, $mday, $hour, $min, $sec);
}
1;