spectre/workflows/priorities RFE

This commit is contained in:
James Tolley 2007-06-26 23:48:41 +00:00
parent 794da40e5c
commit 60eeebdba9
10 changed files with 556 additions and 51 deletions

View file

@ -19,6 +19,7 @@ use DateTime;
use HTTP::Request::Common;
use HTTP::Cookies;
use POE qw(Component::Client::HTTP);
use JSON 'objToJson';
#-------------------------------------------------------------------
@ -29,12 +30,12 @@ Initializes the scheduler.
=cut
sub _start {
my ($kernel, $self, $publicEvents) = @_[ KERNEL, OBJECT, ARG0 ];
$self->debug("Starting Spectre scheduler.");
my $serviceName = "cron";
$kernel->alias_set($serviceName);
$kernel->call( IKC => publish => $serviceName, $publicEvents );
$kernel->yield("checkSchedules");
my ($kernel, $self, $publicEvents) = @_[ KERNEL, OBJECT, ARG0 ];
$self->debug("Starting Spectre scheduler.");
my $serviceName = "cron";
$kernel->alias_set($serviceName);
$kernel->call( IKC => publish => $serviceName, $publicEvents );
$kernel->yield("checkSchedules");
}
#-------------------------------------------------------------------
@ -46,9 +47,9 @@ Gracefully shuts down the scheduler.
=cut
sub _stop {
my ($kernel, $self) = @_[KERNEL, OBJECT];
$self->debug("Stopping the scheduler.");
undef $self;
my ($kernel, $self) = @_[KERNEL, OBJECT];
$self->debug("Stopping the scheduler.");
undef $self;
}
#-------------------------------------------------------------------
@ -318,7 +319,27 @@ sub getJob {
#-------------------------------------------------------------------
=head3 getLogger ( )
=head2 getJsonStatus ( )
Returns JSON of the jobs.
=cut
sub getJsonStatus {
my ($kernel, $request, $self) = @_[KERNEL,ARG0,OBJECT];
my ($sitename, $rsvp) = @$request;
my %data = ();
for my $key (keys %{ $self->{_jobs} }) {
next unless $self->{_jobs}->{$key}->{sitename} eq $sitename;
$data{$key} = $self->{_jobs}->{$key};
}
$kernel->call(IKC => post => $rsvp, objToJson(\%data));
}
#-------------------------------------------------------------------
=head2 getLogger ( )
Returns a reference to the logger.
@ -357,7 +378,7 @@ sub new {
my $debug = shift;
my $self = {_jobs=>{}, _debug=>$debug, _config=>$config, _logger=>$logger};
bless $self, $class;
my @publicEvents = qw(runJob runJobResponse addJob deleteJob);
my @publicEvents = qw(runJob runJobResponse addJob deleteJob getJsonStatus);
POE::Session->create(
object_states => [ $self => [qw(_start _stop runJob runJobResponse addJob deleteJob checkSchedules checkSchedule), @publicEvents] ],
args=>[\@publicEvents]

View file

@ -20,6 +20,7 @@ use HTTP::Cookies;
use POE qw(Component::Client::HTTP);
use POE::Queue::Array;
use Tie::IxHash;
use JSON 'objToJson';
#-------------------------------------------------------------------
@ -177,6 +178,66 @@ sub deleteInstance {
#-------------------------------------------------------------------
=head2 editWorkflowPriority ( href )
Updates the priority of a given workflow instance.
=head3 href
Contains information about the instance and the new priority.
=head4 instanceId
The id of the instance to update.
=head4 newPriority
The new priority value.
=cut
sub editWorkflowPriority {
my ($self, $request, $kernel, $session ) = @_[OBJECT, ARG0, KERNEL, SESSION];
my ($argsHref, $rsvp) = @$request;
my $instanceId = $argsHref->{instanceId};
my $newPriority = $argsHref->{newPriority};
$self->debug("Updating the priority of $instanceId to $newPriority.");
# I'm guessing that the payload can't change queues on us
my $found = 0;
my $filterCref = sub { shift->{instanceId} eq $instanceId };
for my $getQueueMethod (map "get${_}Queue", qw( Suspended Waiting Running )) {
my $q = $self->$getQueueMethod;
my($itemAref) = $q->peek_items($filterCref); # there should be only one
next unless (ref $itemAref eq 'ARRAY' and @$itemAref);
my($priority, $id, $payload) = @$itemAref;
my $ackPriority = $q->set_priority($id, $filterCref, $newPriority);
if ($ackPriority != $newPriority) {
# return an error
my $error = 'edit priority setting error';
$kernel->call(IKC=>post=>$rsvp, objToJson({message => $error}));
}
$found = 1;
last;
}
if (! $found) {
# return an error message
my $error = 'edit priority instance not found error';
$kernel->call(IKC=>post=>$rsvp, objToJson({message => $error}));
}
else {
# return success message
$kernel->call(IKC=>post=>$rsvp, objToJson({message => 'edit priority success'}));
}
}
#-------------------------------------------------------------------
=head2 error ( output )
Prints out error information if debug is enabled.
@ -198,6 +259,44 @@ sub error {
#-------------------------------------------------------------------
=head2 getJsonStatus ( )
Returns JSON report about the workflow engine.
=cut
sub getJsonStatus {
my ($kernel, $request, $self) = @_[KERNEL,ARG0,OBJECT];
my ($sitename, $rsvp) = @$request;
# only return this site's info
return $kernel->call(IKC=>post=>$rsvp, '{}') unless $sitename;
my %queues = ();
tie %queues, 'Tie::IxHash';
%queues = (
Suspended => $self->getSuspendedQueue,
Waiting => $self->getWaitingQueue,
Running => $self->getRunningQueue,
);
my %output = ();
foreach my $queueName (keys %queues) {
my $queue = $queues{$queueName};
my $count = $queue->get_item_count;
my @instances;
if ($count > 0) {
foreach my $itemAref ($queue->peek_items(sub { shift()->{sitename} eq $sitename })) {
push @instances, $itemAref;
}
}
$output{$queueName} = \@instances;
}
$kernel->call(IKC=>post=>$rsvp, objToJson(\%output));
}
#-------------------------------------------------------------------
=head2 getLogger ( )
Returns a reference to the logger.
@ -340,7 +439,7 @@ sub new {
my $debug = shift;
my $self = {_debug=>$debug, _config=>$config, _logger=>$logger};
bless $self, $class;
my @publicEvents = qw(addInstance deleteInstance getStatus);
my @publicEvents = qw(addInstance deleteInstance editWorkflowPriority getStatus getJsonStatus);
POE::Session->create(
object_states => [ $self => [qw(_start _stop returnInstanceToRunnableState addInstance checkInstances deleteInstance suspendInstance runWorker workerResponse), @publicEvents] ],
args=>[\@publicEvents]
@ -431,10 +530,11 @@ Suspends a workflow instance for a number of seconds defined in the config file,
=cut
sub suspendInstance {
my ($self, $instance, $kernel) = @_[OBJECT, ARG0, KERNEL];
$self->debug("Suspending workflow instance ".$instance->{instanceId}." for ".$self->config->get("suspensionDelay")." seconds.");
$self->getSuspendedQueue->enqueue("1", $instance);
$kernel->delay_set("returnInstanceToRunnableState",$self->config->get("suspensionDelay"), $instance);
my ($self, $instance, $kernel) = @_[OBJECT, ARG0, KERNEL];
$self->debug("Suspending workflow instance ".$instance->{instanceId}." for ".$self->config->get("suspensionDelay")." seconds.");
my $priority = ($instance->{priority} - 1) * 10;
$self->getSuspendedQueue->enqueue($priority, $instance);
$kernel->delay_set("returnInstanceToRunnableState",$self->config->get("suspensionDelay"), $instance);
}
#-------------------------------------------------------------------

View file

@ -182,6 +182,15 @@ sub getAdminFunction {
my $self = shift;
my $id = shift;
my $functions = { # at some point in the future we'll need to make this pluggable/configurable
"spectre"=>{
title=>{
id=>"spectre",
namespace=>"Spectre"
},
icon=>"spectre.gif",
op=>"spectreStatus",
group=>"3"
},
"assets"=>{
title=>{
id=>"assets",

View file

@ -227,6 +227,7 @@ sub getOperations {
'spectreGetSiteData' => 'WebGUI::Operation::Spectre',
'spectreTest' => 'WebGUI::Operation::Spectre',
'spectreStatus' => 'WebGUI::Operation::Spectre',
'ssoViaSessionId' => 'WebGUI::Operation::SSO',

View file

@ -81,6 +81,70 @@ sub www_spectreGetSiteData {
return JSON::objToJson(\%siteData,{autoconv=>0, skipinvalid=>1});
}
#-------------------------------------------------------------------
=head2 www_spectreStatus ( )
Show information about Spectre's current workload.
=cut
sub www_spectreStatus {
my $session = shift;
return $session->privilege->adminOnly() unless $session->user->isInGroup(3);
# start to prepare the display
my $ac = WebGUI::AdminConsole->new($session, 'spectre');
my $i18n = WebGUI::International->new($session, 'Spectre');
$session->http->setCacheControl("none");
unless (isInSubnet($session->env->get("REMOTE_ADDR"), $session->config->get("spectreSubnets"))) {
$session->errorHandler->security("make a Spectre workflow runner request, but we're only allowed to accept requests from ".join(",",@{$session->config->get("spectreSubnets")}).".");
return "subnet";
}
my $remote = create_ikc_client(
port=>$session->config->get("spectrePort"),
ip=>$session->config->get("spectreIp"),
name=>rand(100000),
timeout=>10
);
if (!$remote) {
return $ac->render($i18n->get('not running'), $i18n->get('spectre'));
}
my $sitename = $session->config()->get('sitename')->[0];
my $workflowResult = $remote->post_respond('workflow/getJsonStatus',$sitename);
if (!$workflowResult) {
$remote->disconnect();
return $ac->render($i18n->get('workflow status error'), $i18n->get('spectre'));
}
my $cronResult = $remote->post_respond('cron/getJsonStatus',$sitename);
if (! defined $cronResult) {
$remote->disconnect();
return $ac->render($i18n->get('cron status error'), $i18n->get('spectre'));
}
my %data = (
workflow => jsonToObj($workflowResult),
cron => jsonToObj($cronResult),
);
my $workflowCount = @{ $data{workflow}{Suspended} } + @{ $data{workflow}{Waiting} } + @{ $data{workflow}{Running} };
my $workflowUrl = $session->url->page('op=showRunningWorkflows');
my $cronCount = keys %{ $data{cron} };
my $cronUrl = $session->url->page('op=manageCron');
my $output = $i18n->get('running').'<br/>';
$output .= sprintf $i18n->get('workflow header'), $workflowUrl, $workflowCount;
$output .= sprintf $i18n->get('cron header'), $cronUrl, $cronCount;
return $ac->render($output, $i18n->get('spectre'));
}
#-------------------------------------------------------------------
=head2 www_spectreTest ( )

View file

@ -19,6 +19,8 @@ use WebGUI::Workflow;
use WebGUI::Workflow::Activity;
use WebGUI::Workflow::Instance;
use WebGUI::Utility;
use POE::Component::IKC::ClientLite;
use JSON 'jsonToObj';
=head1 NAME
@ -231,6 +233,60 @@ sub www_editWorkflow {
return $ac->render($f->print.$addmenu.$steps, 'edit workflow');
}
#-------------------------------------------------------------------
=head2 www_editWorkflowPriority ( )
Save the submitted new workflow priority.
=cut
sub www_editWorkflowPriority {
my $session = shift;
return $session->privilege->insufficient() unless $session->user->isInGroup(3);
my $i18n = WebGUI::International->new($session, 'Workflow');
my $ac = WebGUI::AdminConsole->new($session,"workflow");
$ac->addSubmenuItem($session->url->page("op=showRunningWorkflows"), $i18n->get('show running workflows'));
$ac->setHelp('manage workflows', 'Workflow');
# make sure the input is good
my $instanceId = $session->form->get('instanceId') || '';
my $newPriority = $session->form->get('newPriority') || '';
if (! $instanceId) {
my $output = $i18n->get('edit priority bad request');
return $ac->render($output, $i18n->get('show running workflows'));
}
# make the request
my $remote = create_ikc_client(
port=>$session->config->get("spectrePort"),
ip=>$session->config->get("spectreIp"),
name=>rand(100000),
timeout=>10
);
if (! $remote) {
my $output = $i18n->get('edit priority no spectre error');
return $ac->render($output, $i18n->get('show running workflows'));
}
my $argHref = {
instanceId => $instanceId,
newPriority => $newPriority,
};
my $resultJson = $remote->post_respond('workflow/editWorkflowPriority', $argHref);
if (! defined $resultJson) {
$remote->disconnect();
my $output = $i18n->get('edit priority no info error');
return $ac->render($output, $i18n->get('show running workflows'));
}
my $responseHref = jsonToObj($resultJson);
my $message = $i18n->get($responseHref->{message}) || $i18n->get('edit priority unknown error');
return $ac->render($message, $i18n->get('show running workflows'));
}
#-------------------------------------------------------------------
@ -397,39 +453,132 @@ Display a list of the running workflow instances.
=cut
sub www_showRunningWorkflows {
my $session = shift;
return $session->privilege->insufficient() unless ($session->user->isInGroup("pbgroup000000000000015"));
my $i18n = WebGUI::International->new($session, "Workflow");
my $output = '<style>
.waiting { color: #808000; }
.complete { color: #008000; }
.error { color: #800000; }
.disabled { color: #808000; }
.done { color: #008000; }
.undefined { color: #800000; }
</style><table style="width: 100%;">';
my $isAdmin = $session->user->isInGroup("3");
my $rs = $session->db->read("select Workflow.title, WorkflowInstance.lastStatus, WorkflowInstance.runningSince, WorkflowInstance.lastUpdate, WorkflowInstance.instanceId from WorkflowInstance left join Workflow on WorkflowInstance.workflowId=Workflow.workflowId order by WorkflowInstance.runningSince desc");
while (my ($title, $status, $runningSince, $lastUpdate, $id) = $rs->array) {
my $class = $status || "complete";
$output .= '<tr class="'.$class.'">'
.'<td>'.$title.'</td>'
.'<td>'.$session->datetime->epochToHuman($runningSince).'</td>';
if ($status) {
$output .= '<td>'
.$status.' / '.$session->datetime->epochToHuman($lastUpdate)
.'</td>';
}
$output .= '<td><a href="'.$session->url->page("op=runWorkflow;instanceId=".$id).'">'.$i18n->get("run").'</a></td>' if ($isAdmin);
$output .= "</tr>\n";
}
$output .= '</table>';
my $ac = WebGUI::AdminConsole->new($session,"workflow");
$ac->addSubmenuItem($session->url->page("op=addWorkflow"), $i18n->get("add a new workflow"));
$ac->addSubmenuItem($session->url->page("op=manageWorkflows"), $i18n->get("manage workflows"));
$ac->setHelp('show running workflows', 'Workflow');
return $ac->render($output, 'show running workflows');
my $session = shift;
return $session->privilege->insufficient() unless ($session->user->isInGroup("pbgroup000000000000015"));
my $i18n = WebGUI::International->new($session, "Workflow");
my $ac = WebGUI::AdminConsole->new($session,"workflow");
my $isAdmin = $session->user->isInGroup("3");
# javascript for creating/showing/hiding the edit priority form
my $cancel = $i18n->get('edit priority cancel');
my $updatePriority = $i18n->get('edit priority update priority');
my $output = <<"ENDCODE";
<style>
.waiting { color: #808000; }
.complete { color: #008000; }
.error { color: #800000; }
.disabled { color: #808000; }
.done { color: #008000; }
.undefined { color: #800000; }
</style>
<script type="text/javascript">
function showEditPriorityForm(iid) {
var alreadyOpenForm = document.getElementById('edit-priority-form');
if (alreadyOpenForm) {
var oldIid = alreadyOpenForm.instanceId.value;
hideEditPriorityForm(oldIid);
}
var ele = document.getElementById('priority-'+iid)
ele.style.display = 'none';
ele.parentNode.insertBefore(getEditPriorityFormNode(iid,ele.innerHTML),ele);
}
function getEditPriorityFormNode(iid,currentPriority) {
var f = document.createElement('form');
f.setAttribute('id','edit-priority-form');
f.setAttribute('method','POST');
f.setAttribute('action','?op=editWorkflowPriority');
f.innerHTML = '<input type="hidden" name="instanceId" value="'+iid+'"/>'+
'<input type="input" name="newPriority" size="3" value="'+currentPriority+'"/>'+
'<input type="submit" value="$updatePriority"/>'+
'<a href="javascript:void(0)" onclick="hideEditPriorityForm(\\''+iid+'\\')">$cancel</a>';
return f;
}
function hideEditPriorityForm(iid) {
var f = document.getElementById('edit-priority-form');
f.parentNode.removeChild(f);
document.getElementById('priority-'+iid).style.display = '';
}
</script>
ENDCODE
my $remote = create_ikc_client(
port=>$session->config->get("spectrePort"),
ip=>$session->config->get("spectreIp"),
name=>rand(100000),
timeout=>10
);
if (! $remote) {
my $output = $i18n->get('spectre not running error');
return $ac->render($output, $i18n->get('show running workflows'));
}
my $sitename = $session->config()->get('sitename')->[0];
my $workflowResult = $remote->post_respond('workflow/getJsonStatus',$sitename);
if (! defined $workflowResult) {
$remote->disconnect();
my $output = $i18n->get('spectre no info error');
return $ac->render($output, $i18n->get('show running workflows'));
}
my $workflowsHref = jsonToObj($workflowResult);
my $workflowTitleFor = $session->db->buildHashRef(<<"");
SELECT wi.instanceId, w.title
FROM WorkflowInstance wi
JOIN Workflow w USING (workflowId)
my $lastActivityFor = $session->db->buildHashRef(<<"");
SELECT wi.instanceId, wa.title
FROM WorkflowInstance wi
JOIN WorkflowActivity wa ON wi.currentActivityId = wa.activityId
for my $workflowType (qw( Suspended Waiting Running )) {
my $workflowsAref = $workflowsHref->{$workflowType};
my $workflowCount = @$workflowsAref;
my $titleHeader = $i18n->get('title header');
my $priorityHeader = $i18n->get('priority header');
my $activityHeader = $i18n->get('activity header');
my $lastStateHeader = $i18n->get('last state header');
my $lastRunTimeHeader = $i18n->get('last run time header');
$output .= sprintf $i18n->get('workflow type count'), $workflowCount, $workflowType;
$output .= '<table style="width: 100%;">';
$output .= "<tr><th>$titleHeader</th><th>$priorityHeader</th><th>$activityHeader</th>";
$output .= "<th>$lastStateHeader</th><th>$lastRunTimeHeader</th></tr>";
for my $workflow (@$workflowsAref) {
my($priority, $id, $instance) = @$workflow;
my $originalPriority = ($instance->{priority} - 1) * 10;
my $instanceId = $instance->{instanceId};
my $title = $workflowTitleFor->{$instanceId} || '(no title)';
my $lastActivity = $lastActivityFor->{$instanceId} || '(none)';
my $lastRunTime = $instance->{lastRunTime} || '(never)';
$output .= '<tr>';
$output .= "<td>$title</td>";
$output .= qq[<td><a id="priority-$instanceId" href="javascript:void(0);" title="Edit Priority" onclick="showEditPriorityForm('$instanceId')">$priority</a>/$originalPriority</td>];
$output .= "<td>$lastActivity</td>";
$output .= "<td>$instance->{lastState}</td>";
$output .= "<td>$lastRunTime</td>";
if ($isAdmin) {
my $run = $i18n->get('run');
my $href = $session->url->page(qq[op=runWorkflow;instanceId=$instanceId]);
$output .= qq[<td><a href="$href">$run</a></td>];
}
$output .= "</tr>\n";
}
$output .= '</table>';
}
$ac->addSubmenuItem($session->url->page("op=addWorkflow"), $i18n->get("add a new workflow"));
$ac->addSubmenuItem($session->url->page("op=manageWorkflows"), $i18n->get("manage workflows"));
$ac->setHelp('show running workflows', 'Workflow');
return $ac->render($output, 'show running workflows');
}
1;

View file

@ -0,0 +1,58 @@
package WebGUI::i18n::English::Spectre; ##Be sure to change the package name to match the filename
our $I18N = { ##hashref of hashes
'spectre' => {
message => q|Spectre|,
lastUpdated => 0,
context => q||,
},
'running' => {
message => q|Spectre is running.|,
lastUpdated => 0,
context => q|let the user know that spectre's off|
},
'not running' => {
message => q|Spectre is not running.|,
lastUpdated => 0,
context => q|let the user know that spectre's off|
},
'workflow status error' => {
message => q|Spectre is running, but there was an error getting the workflow status.|,
lastUpdated => 0,
context => q||,
},
'cron status error' => {
message => q|Spectre is running, but there was an error getting the cron status.|,
lastUpdated => 0,
context => q||,
},
'workflow header' => {
message => q|There are <a href="%s">%d workflows</a>.<br/>|,
lastUpdated => 0,
context => q||,
},
'cron header' => {
message => q|There are <a href="%s">%d scheduled tasks</a>|,
lastUpdated => 0,
context => q||,
},
#If the help file documents an Asset, it must include an assetName key
#If the help file documents an Macro, it must include an macroName key
#If the help file documents a Workflow Activity, it must include an activityName key
#If the help file documents a Template Parser, it must include an templateParserName key
#For all other types, use topicName
'assetName' => {
message => q|This should not matter...?|,
lastUpdated => 1131394072,
},
};
1;

View file

@ -164,8 +164,9 @@ our $I18N = {
'show running workflows body' => {
message => q|
<p>This screen can help you debug problems with workflows by showing which workflows are currently running. The workflows are shown in a table with the name of the workflow, the date it started running. If the workflow has a defined status, then that status will also be shown, along with the date the workflow's status was last updated.</p>
<p>The screen will not automatically update. To update the list of running workflows, reload the page.</p>
<p>This screen can help you debug problems with workflows by showing which workflows are currently running. The workflows are grouped by status, with their names, current and original priorities, current activities (if any), last state, and when the workflow was last run.</p>
<p>You can edit the priority of workflows by clicking on the priority links and submitting the form that appears. You can also run a workflow by clicking the "Run" link in the right column of the table, if present.</p>
<p>The screen will not automatically update. To update the list of running workflows, reload the page.</p>
|,
lastUpdated => 1151719633,
},
@ -195,6 +196,108 @@ and add activities to it.</p>
lastUpdated => 1151721687,
},
'edit priority success' => {
message => q|Workflow priority updated successfully.|,
context => q||,
lastUpdated => 0,
},
'edit priority instance not found error' => {
message => q|I could not find that workflow. Perhaps it's finished running.|,
context => q||,
lastUpdated => 0,
},
'edit priority cancel' => {
message => q|cancel|,
context => q||,
lastUpdated => 0,
},
'edit priority update priority' => {
message => q|Update Priority|,
context => q||,
lastUpdated => 0,
},
'spectre not running error' => {
message => q|Spectre <b>is not running</b>.<br/>Unable to get workflow information.|,
context => q||,
lastUpdated => 0,
},
'spectre no info error' => {
message => q|Spectre <b>is running</b>, but I was not able to get workflow information.|,
context => q||,
lastUpdated => 0,
},
'workflow type count' => {
message => q|<h2>%d %s Workflows</h2>|,
context => q||,
lastUpdated => 0,
},
'title header' => {
message => q|Title|,
context => q||,
lastUpdated => 0,
},
'priority header' => {
message => q|Current/Original Priority|,
context => q||,
lastUpdated => 0,
},
'activity header' => {
message => q|Current Activity|,
context => q||,
lastUpdated => 0,
},
'last state header' => {
message => q|Last State|,
context => q||,
lastUpdated => 0,
},
'last run time header' => {
message => q|Last Run Time|,
context => q||,
lastUpdated => 0,
},
'edit priority setting error' => {
message => q|There was an error setting the new priority.|,
context => q||,
lastUpdated => 0,
},
'edit priority no spectre error' => {
message => q|Spectre <b>is not running</b>.<br/>Unable to get workflow information.|,
context => q||,
lastUpdated => 0,
},
'edit priority bad request' => {
message => q|You have made a bad request.|,
context => q||,
lastUpdated => 0,
},
'edit priority no info error' => {
message => q|Spectre <b>is running</b>, but I was not able to update the priority.|,
context => q||,
lastUpdated => 0,
},
'edit priority unknown error' => {
message => q|There was an unknown error updating the workflow priority. Please try again later.|,
context => q||,
lastUpdated => 0,
},
'topicName' => {
message => q|Workflow|,
context => q|The title of the workflow interface.|,

Binary file not shown.

After

Width:  |  Height:  |  Size: 587 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB