From 02211d5c48e703e691564c69b6ef42a98f5e15b6 Mon Sep 17 00:00:00 2001 From: Lebbeous Fogle-Weekley Date: Mon, 26 Sep 2011 12:06:07 -0400 Subject: [PATCH] Improvement for telephony: just-in-time event revalidation One of the shortcomings with using the Action/Trigger based telephony in Evergreen until now was that while you might have overdue notices generated and sent to a system where Asterisk runs for later calling, but if the notice was generated on a Saturday night, and you have Asterisk set up not to place any calls again until Monday morning, Asterisk has no way of revalidating that call at the last minute. That is, the system could not determine whether the items that were overdue on Saturday night are still overdue on Monday morning, and whether the call should still be made. Now we have a workable solution to that. The eg-pbx-allocator.pl script, which takes call files for Asterisk from a "staging" directory and slowly drips them onto Asterisk's spool can now consult an open-ils.justintime which in turn asks open-ils.trigger whether given events, enumerated within the call files themselves, are still valid. open-ils.trigger is designed to run as a private service, so that's why we need a public service that doesn't do anything too sensitive. This open-ils.justintime service can potentially be extended to offer other just-in-time information to the allocator right before a call goes onto Asterisk's spool. For example, that might be a good time to check the time of day and make a late decision on which phone number to use for a given user (day_phone, evening_phone, other_phone). Signed-off-by: Lebbeous Fogle-Weekley Signed-off-by: Mike Rylander --- Open-ILS/examples/opensrf.xml.example | 22 +++ .../asterisk/pbx-daemon/eg-pbx-allocator.pl | 127 ++++++++++++++++-- .../asterisk/pbx-daemon/eg-pbx-daemon.conf | 1 + .../lib/OpenILS/Application/JustInTime.pm | 33 +++++ .../lib/OpenILS/Application/Trigger.pm | 34 +++++ .../lib/OpenILS/Application/Trigger/Event.pm | 26 ++++ .../OpenILS/Application/Trigger/EventGroup.pm | 19 +++ 7 files changed, 249 insertions(+), 13 deletions(-) create mode 100644 Open-ILS/src/perlmods/lib/OpenILS/Application/JustInTime.pm diff --git a/Open-ILS/examples/opensrf.xml.example b/Open-ILS/examples/opensrf.xml.example index f0107797ff..9a3eaa34e3 100644 --- a/Open-ILS/examples/opensrf.xml.example +++ b/Open-ILS/examples/opensrf.xml.example @@ -191,6 +191,7 @@ vim:et:ts=4:sw=4: open-ils.actor open-ils.auth open-ils.collections + open-ils.justintime @@ -734,6 +735,26 @@ vim:et:ts=4:sw=4: + + 5 + 1 + perl + OpenILS::Application::JustInTime + 199 + + open-ils.justintime_unix.sock + open-ils.justintime_unix.pid + 1000 + open-ils.justintime_unix.log + 1 + 15 + 1 + 5 + + + + + 3 1 @@ -1202,6 +1223,7 @@ vim:et:ts=4:sw=4: open-ils.auth open-ils.storage open-ils.penalty + open-ils.justintime open-ils.cstore open-ils.collections open-ils.ingest diff --git a/Open-ILS/src/asterisk/pbx-daemon/eg-pbx-allocator.pl b/Open-ILS/src/asterisk/pbx-daemon/eg-pbx-allocator.pl index 10d0c170f1..54af53522a 100755 --- a/Open-ILS/src/asterisk/pbx-daemon/eg-pbx-allocator.pl +++ b/Open-ILS/src/asterisk/pbx-daemon/eg-pbx-allocator.pl @@ -79,6 +79,71 @@ Equinox Software, Inc. =cut +package RevalidatorClient; + +use Sys::Syslog qw/:standard :macros/; +use RPC::XML; +use RPC::XML::Client; +use Data::Dumper; + +sub new { + my $self = bless {}, shift; + + $self->setup(@_); + return $self; +} + +sub setup { + my ($self, %config) = @_; + + # XXX error_handler, fault_handler, combined_handler + # such handlers should syslog and die + + $self->{client} = new RPC::XML::Client($config{revalidator_uri}); + $self->{config} = \%config; +} + +sub get_event_ids { + my ($self, $filename) = @_; + + if (not open FH, "<$filename") { + syslog LOG_ERR, "revalidator client could not open $filename"; + die "revalidator client could not open $filename"; + } + + my $result = 0; + while () { + next unless /event_ids = ([\d,]+)$/; + + $result = [ map int, split(/,/, $1) ]; + } + + close FH; + return $result; +} + +sub still_valid { + my ($self, $filename) = @_; + # Here we want to contact Evergreen's open-ils.trigger service and get + # a revalidation of the event described in a given file. + # We'll return 1 for valid, 0 for invalid. + + my $event_ids = $self->get_event_ids($filename) or return 0; + + print STDERR (Dumper($event_ids), "\n") if $self->{config}->{t}; + + my $valid_list = $self->{client}->simple_request( + "open-ils.justintime.events.revalidate", $event_ids + ); + + # NOTE: we require all events to be valid + return (scalar(@$valid_list) == scalar(@$event_ids)) ? 1 : 0; +} + +1; + +package main; + use warnings; use strict; @@ -90,13 +155,13 @@ use File::Spec; use Sys::Syslog qw/:standard :macros/; use Cwd qw/getcwd/; -our %config; -our %opts = ( +my %config; +my %opts = ( c => "/etc/eg-pbx-daemon.conf", v => 0, t => 0, ); -our $universal_prefix = 'EG'; +my $universal_prefix = 'EG'; sub load_config { %config = ParseConfig($opts{c}); @@ -197,27 +262,63 @@ my $out_count = scalar @outgoing; my $limit = $config{queue_limit} || 0; my $available = 0; +my @actually = (); + if ($limit) { $available = $limit - $out_count; - if ($in_count > $available) { - @incoming = @incoming[0..($available-1)]; # slice down to correct size - } if ($available == 0) { $opts{t} or syslog LOG_NOTICE, "Queue is full ($limit)"; } + + if ($config{revalidator_uri}) { # USE REVALIDATOR + # Take as many files from @incoming as it takes to fill up @actually + # with files whose contents describe still-valid events. + + my $revalidator = new RevalidatorClient(%config, %opts); + + for (my $i = 0; $i < $available; $i++) { + while (@incoming) { + my $candidate = shift @incoming; + + if ($revalidator->still_valid($candidate)) { + unshift @actually, $candidate; + last; + } else { + my $newpath = ($config{done_path} || "/tmp") . + "/SKIPPED_" . basename($candidate); + + if ($opts{t}) { + print "rename $candidate $newpath\n"; + } else { + rename($candidate, $newpath); + } + } + } + } + } else { # DON'T USE REVALIDATOR + if ($in_count > $available) { + # slice down to correct size + @actually = @incoming[0..($available-1)]; + } + } } +# XXX Even without a limit we could still filter by still_valid() in theory, +# but in practive the user should always use a limit. + if ($opts{v}) { - printf "incoming (total ): %3d\n", $raw_count; - printf "incoming (future): %3d\n", scalar @future; - printf "incoming (active): %3d\n", $in_count; - printf "queued already : %3d\n", $out_count; - printf "queue_limit : %3d\n", $limit; - printf "available spots : %3s\n", ($limit ? $available : 'unlimited'); + printf "incoming (total) : %3d\n", $raw_count; + printf "incoming (future) : %3d\n", scalar @future; + printf "incoming (active) : %3d\n", $in_count; + printf "incoming (filtered): %3d\n", scalar @actually; + printf "queued already : %3d\n", $out_count; + printf "queue_limit : %3d\n", $limit; + printf "available spots : %3s\n", ($limit ? $available : 'unlimited'); } -foreach (@incoming) { +foreach (@actually) { # $opts{v} and print `ls -l $_`; # ' ', (stat($_))[9], " - $now = ", (stat($_))[9] - $now, "\n"; queue($_); } +1; diff --git a/Open-ILS/src/asterisk/pbx-daemon/eg-pbx-daemon.conf b/Open-ILS/src/asterisk/pbx-daemon/eg-pbx-daemon.conf index e13f444eb2..c7b50eb192 100644 --- a/Open-ILS/src/asterisk/pbx-daemon/eg-pbx-daemon.conf +++ b/Open-ILS/src/asterisk/pbx-daemon/eg-pbx-daemon.conf @@ -7,3 +7,4 @@ group asterisk universal_prefix EG01 queue_limit 30 use_allocator 1 +# revalidator_uri http://somehost/xml-rpc/open-ils.justintime diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/JustInTime.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/JustInTime.pm new file mode 100644 index 0000000000..45b295f163 --- /dev/null +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/JustInTime.pm @@ -0,0 +1,33 @@ +package OpenILS::Application::JustInTime; + +use strict; +use warnings; + +use OpenILS::Application; +use base qw/OpenILS::Application/; +use OpenILS::Application::AppUtils; +my $U = "OpenILS::Application::AppUtils"; + +sub revalidate_events { + my ($self, $conn, $event_id_list) = @_; + + return $U->simplereq( + "open-ils.trigger", + "open-ils.trigger.event_group.revalidate.test", + $event_id_list + ); +} + +__PACKAGE__->register_method( + method => "revalidate_events", + api_name => "open-ils.justintime.events.revalidate", + argc => 1, + signature=> { + params => [ + {type => "array", desc => "list of action_trigger.event IDs"}, + ], + return => { desc => "A list of equal length as the input list telling us whether events validated" } + } +); + +1; diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger.pm index ae83a7755a..ffb45b7fb6 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger.pm @@ -600,6 +600,40 @@ __PACKAGE__->register_method( argc => 1 ); +sub revalidate_event_group_test { + my $self = shift; + my $client = shift; + my $events = shift; + + my $e = OpenILS::Application::Trigger::EventGroup->new(@$events); + + my $result = $e->revalidate_test; + + $e->editor->disconnect; + OpenILS::Application::Trigger::Event->ClearObjectCache(); + + return $result; +} +__PACKAGE__->register_method( + api_name => 'open-ils.trigger.event_group.revalidate.test', + method => 'revalidate_event_group_test', + api_level=> 1, + argc => 1, + signature => { + desc => q/revalidate a group of events. + This does not actually update the events (so there will be no change + of atev.state or anything else in the database, unless an event's + validator makes changes out-of-band). + + This returns an array of valid event IDs. + /, + params => [ + {name => "events", type => "array", desc => "list of event ids"} + ] + } +); + + sub pending_events { my $self = shift; my $client = shift; diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/Event.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/Event.pm index de11a15eac..3e98ad7c78 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/Event.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/Event.pm @@ -227,6 +227,32 @@ sub validate { return $self; } +sub revalidate_test { + my $self = shift; + + if ($self->build_environment->environment->{complete}) { + try { + $self->valid( + OpenILS::Application::Trigger::ModRunner::Validator->new( + $self->event->event_def->validator, + $self->environment + )->run->final_result + ); + } otherwise { + $log->error("Event revalidation failed with ". shift()); + }; + + return 1 if defined $self->valid and $self->valid; + return 0; + } + + $logger->error( + "revalidate: could not build environment for event " . + $self->event->id + ); + return 0; +} + sub cleanedup { my $self = shift; return undef unless (ref $self); diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/EventGroup.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/EventGroup.pm index 2a3c6c632c..5703854915 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/EventGroup.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/EventGroup.pm @@ -98,6 +98,25 @@ sub validate { return $self; } +sub revalidate_test { + my $self = shift; + + $self->editor->xact_begin; + + my @valid_events; + try { + for my $event ( @{ $self->events } ) { + push @valid_events, $event->id if $event->revalidate_test; + } + $self->editor->xact_rollback; + } otherwise { + $log->error("Event group validation failed with ". shift()); + $self->editor->xact_rollback; + }; + + return \@valid_events; +} + sub cleanedup { my $self = shift; return undef unless (ref $self); -- 2.43.2