Improvement for telephony: just-in-time event revalidation
authorLebbeous Fogle-Weekley <lebbeous@esilibrary.com>
Mon, 26 Sep 2011 16:06:07 +0000 (12:06 -0400)
committerMike Rylander <mrylander@gmail.com>
Tue, 15 Nov 2011 21:30:00 +0000 (16:30 -0500)
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 <lebbeous@esilibrary.com>
Signed-off-by: Mike Rylander <mrylander@gmail.com>

Open-ILS/examples/opensrf.xml.example
Open-ILS/src/asterisk/pbx-daemon/eg-pbx-allocator.pl
Open-ILS/src/asterisk/pbx-daemon/eg-pbx-daemon.conf
Open-ILS/src/perlmods/lib/OpenILS/Application/JustInTime.pm [new file with mode: 0644]
Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/Event.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Trigger/EventGroup.pm

index f010779..9a3eaa3 100644 (file)
@@ -191,6 +191,7 @@ vim:et:ts=4:sw=4:
                 <service>open-ils.actor</service>
                 <service>open-ils.auth</service>
                 <service>open-ils.collections</service>
+                <service>open-ils.justintime</service>
             </allowed_services>
         </xml-rpc>
 
@@ -734,6 +735,26 @@ vim:et:ts=4:sw=4:
                 </app_settings>
             </open-ils.penalty>
 
+            <open-ils.justintime>
+                <keepalive>5</keepalive>
+                <stateless>1</stateless>
+                <language>perl</language>
+                <implementation>OpenILS::Application::JustInTime</implementation>
+                <max_requests>199</max_requests>
+                <unix_config>
+                    <unix_sock>open-ils.justintime_unix.sock</unix_sock>
+                    <unix_pid>open-ils.justintime_unix.pid</unix_pid>
+                    <max_requests>1000</max_requests>
+                    <unix_log>open-ils.justintime_unix.log</unix_log>
+                    <min_children>1</min_children>
+                    <max_children>15</max_children>
+                    <min_spare_children>1</min_spare_children>
+                    <max_spare_children>5</max_spare_children>
+                </unix_config>
+                <app_settings>
+                </app_settings>
+            </open-ils.justintime>
+
             <open-ils.circ> 
                 <keepalive>3</keepalive>
                 <stateless>1</stateless>
@@ -1202,6 +1223,7 @@ vim:et:ts=4:sw=4:
                 <appname>open-ils.auth</appname> 
                 <appname>open-ils.storage</appname>  
                 <appname>open-ils.penalty</appname>  
+                <appname>open-ils.justintime</appname>  
                 <appname>open-ils.cstore</appname>  
                 <appname>open-ils.collections</appname>  
                 <appname>open-ils.ingest</appname>  
index 10d0c17..54af535 100755 (executable)
@@ -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 (<FH>) {
+        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;
index e13f444..c7b50eb 100644 (file)
@@ -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 (file)
index 0000000..45b295f
--- /dev/null
@@ -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;
index ae83a77..ffb45b7 100644 (file)
@@ -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;
index de11a15..3e98ad7 100644 (file)
@@ -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);
index 2a3c6c6..5703854 100644 (file)
@@ -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);