From raeburn at source.lon-capa.org Mon Nov 12 22:59:17 2018 From: raeburn at source.lon-capa.org (raeburn) Date: Tue, 13 Nov 2018 03:59:17 -0000 Subject: [LON-CAPA-cvs] cvs: rat / lonpageflip.pm lonuserstate.pm /client parameter.html loncom/interface lonnavmaps.pm lonparmset.pm lonquickgrades.pm loncom/misc releaseslist.xml loncom/publisher packages.tab Message-ID: raeburn Tue Nov 13 03:59:17 2018 EDT Modified files: /loncom/interface lonnavmaps.pm lonparmset.pm lonquickgrades.pm /loncom/publisher packages.tab /loncom/misc releaseslist.xml /rat/client parameter.html /rat lonuserstate.pm lonpageflip.pm Log: - Bug 6400 - deeplink parameter determines whether deep-linked items are listed in and/or linked to in Course Contents and student's view of Grades -------------- next part -------------- Index: loncom/interface/lonnavmaps.pm diff -u loncom/interface/lonnavmaps.pm:1.542 loncom/interface/lonnavmaps.pm:1.543 --- loncom/interface/lonnavmaps.pm:1.542 Sat Apr 14 17:52:43 2018 +++ loncom/interface/lonnavmaps.pm Tue Nov 13 03:59:00 2018 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Navigate Maps Handler # -# $Id: lonnavmaps.pm,v 1.542 2018/04/14 17:52:43 raeburn Exp $ +# $Id: lonnavmaps.pm,v 1.543 2018/11/13 03:59:00 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -963,32 +963,32 @@ $newBranchText = ".mt('Branch')."; } - # links to open and close the folder - my $whitespace = $location.'/whitespace_21.gif'; - my $linkopen = ""; - my $nomodal; - if (($params->{'modalLink'}) && (!$resource->is_sequence())) { - if ($link =~m{^(?:|/adm/wrapper)/ext/([^#]+)}) { - my $exturl = $1; - if (($ENV{'SERVER_PORT'} == 443) && ($exturl !~ /^https:/)) { + my ($nomodal,$linkopen,$linkclose); + unless ($resource->is_map() || $params->{'resource_nolink'}) { + $linkopen = ""; + $linkclose = ""; + if (($params->{'modalLink'}) && (!$resource->is_sequence())) { + if ($link =~m{^(?:|/adm/wrapper)/ext/([^#]+)}) { + my $exturl = $1; + if (($ENV{'SERVER_PORT'} == 443) && ($exturl !~ /^https:/)) { + $nomodal = 1; + } + } elsif (($link eq "/public/$LONCAPA::match_domain/$LONCAPA::match_courseid/syllabus") && + ($env{'request.course.id'}) && ($ENV{'SERVER_PORT'} == 443) && + ($env{'course.'.$env{'request.course.id'}.'.externalsyllabus'} =~ m{^http://})) { $nomodal = 1; } - } elsif (($link eq "/public/$LONCAPA::match_domain/$LONCAPA::match_courseid/syllabus") && - ($env{'request.course.id'}) && ($ENV{'SERVER_PORT'} == 443) && - ($env{'course.'.$env{'request.course.id'}.'.externalsyllabus'} =~ m{^http://})) { - $nomodal = 1; - } - my $esclink = &js_escape($link); - if ($nomodal) { - $linkopen .= ""; + my $esclink = &js_escape($link); + if ($nomodal) { + $linkopen .= ""; + } else { + $linkopen .= ""; + } } else { - $linkopen .= ""; + $linkopen .= ""; } - } else { - $linkopen .= ""; } - my $linkclose = ""; # Default icon: unknown page my $icon = ""; @@ -1036,13 +1036,14 @@ '&jump=' . &escape($resource->symb()) . "&folderManip=1\">"; - + $linkclose = ''; } else { # Don't allow users to manipulate folder $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.gif'; $icon = ""."\"".($nowOpen"; if ($params->{'caller'} eq 'sequence') { $linkopen = ""; + $linkclose = ''; } else { $linkopen = ""; $linkclose = ""; @@ -1061,10 +1062,15 @@ } if ($params->{'mapHidden'} || $resource->randomout()) { $nonLinkedText .= ' ('.&mt('hidden').') '; + } elsif ($params->{'mapUnlisted'}) { + $nonLinkedText .= ' ('.&mt('unlisted').') '; } } else { if ($resource->randomout()) { $nonLinkedText .= ' ('.&mt('hidden').') '; + } elsif (($resource->deeplink($params->{caller}) eq 'absent') || + ($resource->deeplink($params->{caller}) eq 'grades')) { + $nonLinkedText .= ' ('.&mt('unlisted').') '; } } if (!$resource->condval()) { @@ -1388,7 +1394,9 @@ # Without renaming the filterfunc, the server seems to go into # an infinite loop my $oldFilterFunc = $filterFunc; - $filterFunc = sub { my $res = shift; return !$res->randomout() && + $filterFunc = sub { my $res = shift; return !$res->randomout() && + ($res->deeplink($args->{'caller'}) ne 'absent') && + ($res->deeplink($args->{'caller'}) ne 'grades') && &$oldFilterFunc($res);}; } @@ -1805,6 +1813,7 @@ # If this is an empty sequence and we're filtering them, continue on $args->{'mapHidden'} = 0; + $args->{'mapUnlisted'} = 0; if (($curRes->is_map()) && (!$curRes->{DATA}->{HAS_VISIBLE_CHILDREN})) { if ($args->{'suppressEmptySequences'}) { next; @@ -1817,6 +1826,15 @@ } else { next; } + } else { + my $deeplink = $navmap->get_mapparam(undef,$mapname,"0.deeplink"); + if (($deeplink eq 'absent') || ($deeplink eq 'grades')) { + if ($userCanSeeHidden) { + $args->{'mapUnlisted'} = 1; + } else { + next; + } + } } } } @@ -1879,8 +1897,17 @@ $args->{'condensed'} = 1; } } - } - + } + # If deep-link parameter is set (and is not set to full) suppress link + # unless priviliged user, or calling context is sequence, and parameter + # set at map level + if ((!$curRes->deeplink($args->{'caller'})) || + ($curRes->deeplink($args->{'caller'}) eq 'full') || &advancedUser()) { + $args->{'resource_nolink'} = 0; + } else { + $args->{'resource_nolink'} = 1; + } + # If the multipart problem was condensed, "forget" it was multipart if (scalar(@parts) == 1) { $args->{'multipart'} = 0; @@ -4692,7 +4719,6 @@ sub is_empty_sequence { my $self=shift; - my $src = $self->src(); return !$self->is_page() && $self->navHash("is_map_", 1) && !$self->navHash("map_type_" . $self->map_pc()); } @@ -5112,6 +5138,17 @@ my $available = $self->parmval("available", $part); return ($useslots,$availablestudent,$available); } +sub deeplink { + my ($self,$caller) = @_; + if ($caller eq 'sequence') { + my @deeplink = $self->parmval("deeplink"); + if ($deeplink[1] eq 'resource') { + return $deeplink[0]; + } + } else { + return $self->parmval("deeplink"); + } +} # Multiple things need this sub getReturnHash { Index: loncom/interface/lonparmset.pm diff -u loncom/interface/lonparmset.pm:1.586 loncom/interface/lonparmset.pm:1.587 --- loncom/interface/lonparmset.pm:1.586 Fri Sep 14 18:27:49 2018 +++ loncom/interface/lonparmset.pm Tue Nov 13 03:59:00 2018 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Handler to set parameters for assessments # -# $Id: lonparmset.pm,v 1.586 2018/09/14 18:27:49 raeburn Exp $ +# $Id: lonparmset.pm,v 1.587 2018/11/13 03:59:00 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -2101,6 +2101,7 @@ 'buttonshide' => 'hiding', 'turnoffeditor' => 'hiding', 'encrypturl' => 'hiding', + 'deeplink' => 'hiding', 'randomorder' => 'high_level_randomization', 'randompick' => 'high_level_randomization', 'available' => 'slots', @@ -4660,8 +4661,15 @@ ['no','No']], 'string_ip' => [['_allowfrom_','Hostname(s), or IP(s) from which access is allowed'], - ['_denyfrom_',], 'Hostname(s) or IP(s) from which access is disallowed'], - ); + ['_denyfrom_','Hostname(s) or IP(s) from which access is disallowed']], + 'string_deeplink' + => [['full','Displayed (linked) in Contents and Grades'], + ['absent','Not displayed in Contents or Grades'], + ['grades','Displayed in Grades only'], + ['details','Displayed (unlinked) in Contents and Grades'], + ['datestatus','Displayed (with status), but unlinked in Contents and Grades']], + ); + my %stringmatches = ( 'string_lenient' @@ -4678,6 +4686,7 @@ discussvote => 'string_discussvote', examcode => 'string_examcode', acc => 'string_ip', + deeplink => 'string_deeplink', ); # Returns the possible values and titles for a given string type, or undef if there are none. Index: loncom/interface/lonquickgrades.pm diff -u loncom/interface/lonquickgrades.pm:1.113 loncom/interface/lonquickgrades.pm:1.114 --- loncom/interface/lonquickgrades.pm:1.113 Sun Dec 31 15:49:03 2017 +++ loncom/interface/lonquickgrades.pm Tue Nov 13 03:59:00 2018 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Quick Student Grades Display # -# $Id: lonquickgrades.pm,v 1.113 2017/12/31 15:49:03 raeburn Exp $ +# $Id: lonquickgrades.pm,v 1.114 2018/11/13 03:59:00 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -444,7 +444,8 @@ if ($curRes == $iterator->BEGIN_MAP()) {$depth++;} if ($curRes == $iterator->END_MAP()) { $depth--; } - if (ref($curRes) && $curRes->is_gradable() && !$curRes->randomout) + if (ref($curRes) && $curRes->is_gradable() && !$curRes->randomout && + ($curRes->deeplink ne 'absent')) { # Get number of correct, incorrect parts my $parts = $curRes->parts(); Index: loncom/publisher/packages.tab diff -u loncom/publisher/packages.tab:1.78 loncom/publisher/packages.tab:1.79 --- loncom/publisher/packages.tab:1.78 Mon Dec 18 23:13:58 2017 +++ loncom/publisher/packages.tab Tue Nov 13 03:59:04 2018 @@ -70,6 +70,8 @@ part_0&buttonshide&display:Hide buttons from students part_0&buttonshide&type:string_yesno part_0&buttonshide&default:no +part_0&deeplink&display:Deep-linked items +part_0&deeplink&type:string_deeplink numericalhint&tol&display:Numerical Tolerance numericalhint&tol&type:tolerance numericalhint&tol&default:5% @@ -166,6 +168,8 @@ extension_page&randomorder&display:Randomly Order Resources extension_page&examcode&type:string_examcode extension_page&examcode&display:Exam Code (graded CODEd exam containing randomorder/randompick). +extension_page&deeplink&display:Deep-linked items +extension_page&deeplink&type:string_deeplink import_defaults&extension_html:1 extension_html&cssfile&display:CSS file to link @@ -263,6 +267,8 @@ default&buttonshide&display:Hide buttons from students default&buttonshide&type:string_yesno default&buttonshide&default:no +default&deeplink&display:Deep-linked items +default&deeplink&type:string_deeplink default&acc&display:Client IP/Name Access Control default&acc&type:string_ip #default&hiddenresource&hidden:parm @@ -313,4 +319,7 @@ Task_0&availablestudent&type:string Task_0&cssfile&display:CSS file to link Task_0&cssfile&type:string +Task_0&deeplink&display:Deep-linked items +Task_0&deeplink&type:string_deeplink + Index: loncom/misc/releaseslist.xml diff -u loncom/misc/releaseslist.xml:1.17 loncom/misc/releaseslist.xml:1.18 --- loncom/misc/releaseslist.xml:1.17 Tue Jan 2 14:43:24 2018 +++ loncom/misc/releaseslist.xml Tue Nov 13 03:59:08 2018 @@ -24,6 +24,11 @@ 2.12 2.12 2.12 +2.12 +2.12 +2.12 +2.12 +2.12 2.1 2.2 2.10 Index: rat/client/parameter.html diff -u rat/client/parameter.html:1.77 rat/client/parameter.html:1.78 --- rat/client/parameter.html:1.77 Sat Jul 15 02:19:00 2017 +++ rat/client/parameter.html Tue Nov 13 03:59:12 2018 @@ -5,7 +5,7 @@ The LearningOnline Network with CAPA Parameter Input Window // -// $Id: parameter.html,v 1.77 2017/07/15 02:19:00 raeburn Exp $ +// $Id: parameter.html,v 1.78 2018/11/13 03:59:12 raeburn Exp $ // // Copyright Michigan State University Board of Trustees // @@ -1059,6 +1059,31 @@ choicewrite(' /> Yes, and the scope of student selected slot is the enclosing map/folder. When checking in, all resources in the map/folder are checked in.
'); choicewrite(''); } + if (pscat=='deeplink') { + tablestart('Deep-linked items'); + choicewrite('Value:'); + choicewrite('
'); + choicewrite('
'); + choicewrite('
'); + choicewrite('
'); + choicewrite('
'); + choicewrite(''); + } } if (ptype=='color') { @@ -1384,6 +1409,7 @@ else if (pscat == 'ip') { sopt('ip','IP Number/Name'); } else if (pscat == 'fileext') { sopt('fileext','File Extension'); } else if (pscat == 'useslots') { sopt('useslots','Slots control access'); } + else if (pscat == 'deeplink') { sopt('deeplink','Deep-linked items'); } else { pscat = 'any'; } sopt('any','String Value'); } Index: rat/lonuserstate.pm diff -u rat/lonuserstate.pm:1.156 rat/lonuserstate.pm:1.157 --- rat/lonuserstate.pm:1.156 Thu Mar 29 21:12:57 2018 +++ rat/lonuserstate.pm Tue Nov 13 03:59:17 2018 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Construct and maintain state and binary representation of course for user # -# $Id: lonuserstate.pm,v 1.156 2018/03/29 21:12:57 raeburn Exp $ +# $Id: lonuserstate.pm,v 1.157 2018/11/13 03:59:17 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -62,6 +62,7 @@ my %randomizationcode; # code used to grade folder for bubblesheet exam my %encurl; # URLs in this folder are supposed to be encrypted my %hiddenurl; # this URL (or complete folder) is supposed to be hidden +my %deeplinkonly; # this URL (or complete folder) is deep-link only my %rescount; # count of unhidden items in each map my %mapcount; # count of unhidden maps in each map @@ -917,6 +918,14 @@ && ($hash{'src_'.$rid}!~/\.sequence$/)) { $retfrid=$rid; } + my @deeplink=&Apache::lonnet::EXT('resource.0.deeplink',$symb); + unless ((@deeplink == 0) || ($deeplink[0] eq 'full')) { + $deeplinkonly{$rid}=join(':', at deeplink); + if ($deeplink[1] eq 'map') { + my $parent = (split(/\,/,$hash{'map_hierarchy_'.$mapid}))[-1]; + $deeplinkonly{"$parent.$mapid"}=$deeplinkonly{$rid}; + } + } if (defined($hash{'conditions_'.$rid})) { $hash{'conditions_'.$rid}=simplify( @@ -1232,6 +1241,7 @@ undef %randomizationcode; undef %hiddenurl; undef %encurl; + undef %deeplinkonly; undef %rescount; undef %mapcount; $retfrid=''; @@ -1380,6 +1390,7 @@ undef %randomizationcode; undef %hiddenurl; undef %encurl; + undef %deeplinkonly; undef %rescount; undef %mapcount; $errtext=''; @@ -1518,6 +1529,10 @@ # $hash{'src_'.$id}=&Apache::lonenc::encrypted($hash{'src_'.$id}); $hash{'encrypted_'.$id}=1; } +# ------------------------------------------------------------ Deep-linked URLs + foreach my $id (keys(%deeplinkonly)) { + $hash{'deeplinkonly_'.$id}=$deeplinkonly{$id}; + } # ----------------------------------------------- Close hashes to finally store # --------------------------------- Routine must pass this point, no early outs $hash{'first_rid'}=$retfrid; Index: rat/lonpageflip.pm diff -u rat/lonpageflip.pm:1.97 rat/lonpageflip.pm:1.98 --- rat/lonpageflip.pm:1.97 Thu Nov 16 13:42:01 2017 +++ rat/lonpageflip.pm Tue Nov 13 03:59:17 2018 @@ -2,7 +2,7 @@ # # Page flip handler # -# $Id: lonpageflip.pm,v 1.97 2017/11/16 13:42:01 raeburn Exp $ +# $Id: lonpageflip.pm,v 1.98 2018/11/13 03:59:17 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -102,6 +102,9 @@ my ($next,$endupmap,$direction) = @_; my $safecount=0; my $allowed=0; + my $deeplinkonly=0; + my $prev=$next; + my ($prevmapid)=split(/\./,$next); do { ($next,$endupmap)=&get_next_possible_move($next,$endupmap,$direction); @@ -115,14 +118,35 @@ my $priv = &Apache::lonnet::allowed('bre',$url,$symb); $allowed = (($priv eq 'F') || ($priv eq '2')); } + $deeplinkonly = 0; + if ($hash{'deeplinkonly_'.$next}) { + my ($value,$level) = split(/:/,$hash{'deeplinkonly_'.$next}); + if ($level eq 'resource') { + $deeplinkonly = 1; + } elsif ($level eq 'map') { + if ($mapid != $prevmapid) { + $deeplinkonly = 1; + } + } + } elsif ($hash{'deeplinkonly_'.$prev}) { + my ($value,$level) = split(/:/,$hash{'deeplinkonly_'.$prev}); + if ($level eq 'resource') { + $deeplinkonly = 1; + } elsif ($level eq 'map') { + if ($mapid != $prevmapid) { + $deeplinkonly = 1; + } + } + } $safecount++; } while ( ($next) && ($next!~/\,/) && ( (!$hash{'src_'.$next}) || ( - (!$env{'request.role.adv'}) - && $hash{'randomout_'.$next} + (!$env{'request.role.adv'}) + && (($hash{'randomout_'.$next}) + || ($deeplinkonly)) ) || (!$allowed) ) @@ -338,7 +362,7 @@ my $multichoice=0; my %multichoicehash=(); my %prog_state=(); - my ($redirecturl,$redirectsymb,$enc,$anchor); + my ($redirecturl,$redirectsymb,$enc,$anchor,$deeplinklevel); my $next=''; my $hostname = $r->hostname(); my @possibilities=(); @@ -564,6 +588,9 @@ } else { # -------------------------------------------------------------- No place to go $multichoice=-1; + if ($hash{'deeplinkonly_'.$rid}) { + (my $value,$deeplinklevel) = split(/:/,$hash{'deeplinkonly_'.$rid}); + } } # ----------------- The program must come past this point to untie the big hash untie(%hash); @@ -618,6 +645,11 @@ &Apache::loncommon::content_type($r,'text/html'); $r->send_http_header; my %lt=&Apache::lonlocal::texthash('title' => 'End of Sequence', + 'deeplink' => 'No link available', + 'deeplinkres' => + 'Navigation to other content is unavailable when accessing content via deep-linking', + 'deeplinkmap' => + 'You have reached the end of the sequence of available materials for access via deep-linking', 'explain' => 'You have reached the end of the sequence of materials.', 'back' => 'Go Back', @@ -685,17 +717,29 @@ } else { $r->print(&Apache::lonplacementtest::showresult(1)); } - } else { + } else { $r->print( - &Apache::loncommon::start_page('No Resource') - .'

'.$lt{'title'}.'

' - .'

'.$lt{'explain'}.'

'); + &Apache::loncommon::start_page('No Resource')); + if ($deeplinklevel eq 'resource') { + $r->print('

'.$lt{'deeplink'}.'

' + .'

'.$lt{'deeplinkres'}.'

'); + } elsif ($deeplinklevel eq 'map') { + $r->print('

'.$lt{'title'}.'

' + .'

'.$lt{'deeplinkmap'}.'

'); + } else { + $r->print('

'.$lt{'title'}.'

' + .'

'.$lt{'explain'}.'

'); + } } } } unless (($env{'course.'.$env{'request.course.id'}.'.type'} eq 'Placement') || ($env{'request.role.adv'})) { - if ((!@possibilities) && ($reinitcheck)) { + if ($deeplinklevel) { + $r->print( + &Apache::lonhtmlcommon::actionbox( + [''.$lt{'back'}.''])); + } elsif ((!@possibilities) && ($reinitcheck)) { $r->print( &Apache::lonhtmlcommon::actionbox( [''.$lt{'nav'}.'' From raeburn at source.lon-capa.org Sun Nov 18 17:50:54 2018 From: raeburn at source.lon-capa.org (raeburn) Date: Sun, 18 Nov 2018 22:50:54 -0000 Subject: [LON-CAPA-cvs] cvs: loncom / loncron /interface domainprefs.pm loncommon.pm Message-ID: raeburn Sun Nov 18 22:50:54 2018 EDT Modified files: /loncom loncron /loncom/interface loncommon.pm domainprefs.pm Log: - Scanning of lonnet.perm.log by loncron - Information about which nodes have unsent update transactions is logged. - Specific nodes can be excluded from unsend count (for delayed updates) via domain configuration. - Weight for unsend count (before adding to "warnings") is configurable. -------------- next part -------------- Index: loncom/loncron diff -u loncom/loncron:1.113 loncom/loncron:1.114 --- loncom/loncron:1.113 Tue Nov 6 15:37:42 2018 +++ loncom/loncron Sun Nov 18 22:50:46 2018 @@ -2,7 +2,7 @@ # Housekeeping program, started by cron, loncontrol and loncron.pl # -# $Id: loncron,v 1.113 2018/11/06 15:37:42 raeburn Exp $ +# $Id: loncron,v 1.114 2018/11/18 22:50:46 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -43,6 +43,7 @@ use Getopt::Long; use GDBM_File; use Storable qw(thaw); +use File::ReadBackwards; #globals use vars qw (%perlvar %simplestatus $errors $warnings $notices $totalcount); @@ -646,35 +647,199 @@ # ------------------------------------------------------------ Delayed messages sub check_delayed_msg { - my ($fh)=@_; + my ($fh,$weightsref,$exclusionsref)=@_; &log($fh,'

Delayed Messages

'); print "Checking buffers.\n"; &log($fh,'

Scanning Permanent Log

'); my $unsend=0; + my $ignored=0; my %hostname = &Apache::lonnet::all_hostnames(); my $numhosts = scalar(keys(%hostname)); - - my $dfh=IO::File->new("$perlvar{'lonDaemons'}/logs/lonnet.perm.log"); - while (my $line=<$dfh>) { - my ($time,$sdf,$dserv,$dcmd)=split(/:/,$line); - if ($numhosts) { - next unless ($hostname{$dserv}); - } - if ($sdf eq 'F') { - my $local=localtime($time); - &log($fh,"Failed: $time, $dserv, $dcmd
"); - $warnings++; - } - if ($sdf eq 'S') { $unsend--; } - if ($sdf eq 'D') { $unsend++; } + my $checkbackwards = 0; + my $checkfrom = 0; + my $checkexcluded = 0; + my (%bymachine,%weights,%exclusions,%serverhomes); + if (ref($weightsref) eq 'HASH') { + %weights = %{$weightsref}; + } + if (ref($exclusionsref) eq 'HASH') { + %exclusions = %{$exclusionsref}; + if (keys(%exclusions)) { + $checkexcluded = 1; + %serverhomes = &read_serverhomeIDs(); + } } - &log($fh,"

Total unsend messages: $unsend

\n"); - if ($unsend > 0) { - $warnings=$warnings+5*$unsend; +# +# For LON-CAPA 1.2.0 to 2.1.3 (release dates: 8/31/2004 and 3/31/2006) any +# entry logged in lonnet.perm.log for completion of a delayed (critical) +# transaction lacked the hostID for the remote node to which the command +# to be completed was sent. +# +# Because of this, exclusion of items in lonnet.perm.log for nodes which are +# no longer part of the cluster from adding to the overall "unsend" count +# needs additional effort besides the changes made in loncron rev. 1.105. +# +# For "S" (completion) events logging in LON-CAPA 1.2.0 through 2.1.3 included +# "LondTransaction=HASH(hexadecimal)->getClient() :$cmd, where the hexadecimal +# is a memory location, and $cmd is the command sent to the remote node. +# +# Starting with 2.2.0 (released 8/21/2006) logging for "S" (completion) events +# had sethost:$host_id:$cmd after LondTransaction=HASH(hexadecimal)->getClient() +# +# Starting with 2.4.1 (released 6/13/2007) logging for "S" replaced echoing the +# getClient() call with the result of the Transaction->getClient() call itself +# undef for completion of delivery of a delayed message. +# +# The net effect of these changes is that lonnet.perm.log is now accessed three +# times: (a) oldest record is checked, if earlier than release date for 2.5.0 +# then (b) file is read backwards, with timestamp recorded for most recent +# instance of logged "S" event for "update" command without "sethost:$host_id:" +# then (c) file is read forward with records ignored which predate the timestamp +# recorded in (b), if one was found. +# +# In (c), when calculating the unsend total, i.e., the difference between delayed +# transactions ("D") and sent transactions ("S"), transactions are ignored if the +# target node is no longer in the cluster, and also (for "update" commands), if +# the target node is in the list of nodes excluded from the count, in the domain +# configuration for this machine's default domain. The idea here is to remove +# delayed "update" commands for nodes for which inbound access to port 5663, +# is blocked, but are still part of the LON-CAPA network, (i.e., they can still +# replicate content from other nodes). +# + + my $dfh=IO::File->new("$perlvar{'lonDaemons'}/logs/lonnet.perm.log","r"); + if (defined($dfh)) { + while (my $line=<$dfh>) { + my ($time,$sdf,$rest)=split(/:/,$line,3); + if ($time < 1541185772) { + $checkbackwards = 1; + } + last; + } + undef $dfh; + } + + if ($checkbackwards) { + if (tie *BW, 'File::ReadBackwards', "$perlvar{'lonDaemons'}/logs/lonnet.perm.log") { + while(my $line=) { + if ($line =~ /\QLondTransaction=HASH\E[^:]+:update:/) { + ($checkfrom) = split(/:/,$line,2); + last; + } + } + close(BW); + } + } + $dfh=IO::File->new("$perlvar{'lonDaemons'}/logs/lonnet.perm.log","r"); + if (defined($dfh)) { + while (my $line=<$dfh>) { + my ($time,$sdf,$rest)=split(/:/,$line,3); + next unless (($sdf eq 'F') || ($sdf eq 'S') || ($sdf eq 'D')); + next if (($checkfrom) && ($time <= $checkfrom)); + my ($dserv,$dcmd); + if ($sdf eq 'S') { + my ($serva,$cmda,$servb,$cmdb) = split(/:/,$rest); + if ($cmda eq 'sethost') { + chomp($cmdb); + $dcmd = $cmdb; + } else { + $dcmd = $cmda; + } + if (($serva =~ /^LondTransaction/) || ($serva eq '')) { + unless (($servb eq '') || ($servb =~ m{^/})) { + $dserv = $servb; + } + } else { + $dserv = $serva; + } + } else { + ($dserv,$dcmd) = split(/:/,$rest); + } + if ($sdf eq 'F') { + my $local=localtime($time); + &log($fh,"Failed: $time, $dserv, $dcmd
"); + $warnings++; + } + next if ((($dserv eq '') || ($dcmd eq '')) && ($sdf ne 'F')); + if ($sdf eq 'S') { + if ($dcmd eq 'update') { + if ($hostname{$dserv}) { + if ($exclusions{$serverhomes{$hostname{$dserv}}}) { + $ignored --; + } else { + $unsend --; + } + } + if (exists($bymachine{$dserv})) { + $bymachine{$dserv} --; + } else { + $bymachine{$dserv} = -1; + } + } else { + if ($hostname{$dserv}) { + $unsend --; + } + } + } elsif ($sdf eq 'D') { + if ($dcmd eq 'update') { + if ($hostname{$dserv}) { + if ($exclusions{$serverhomes{$hostname{$dserv}}}) { + $ignored ++; + } else { + $unsend ++; + } + } + if (exists($bymachine{$dserv})) { + $bymachine{$dserv} ++; + } else { + $bymachine{$dserv} = 1; + } + } else { + if ($hostname{$dserv}) { + $unsend ++; + } + } + } + } + undef $dfh; + my $nodest = 0; + my $retired = 0; + my %active; + if (keys(%bymachine)) { + unless ($checkexcluded) { + %serverhomes = &read_serverhomeIDs(); + } + foreach my $key (keys(%bymachine)) { + if ($bymachine{$key} > 0) { + if ($hostname{$key}) { + $active{$serverhomes{$hostname{$key}}} += $bymachine{$key}; + } else { + $retired ++; + $nodest += $bymachine{$key}; + } + } + } + } + if (keys(%active)) { + &log($fh,"

Unsend messages by node, active (undegraded) nodes in cluster

\n"); + foreach my $key (sort(keys(%active))) { + &log($fh,&encode_entities("$key => $active{$key}",'<>&"')."\n"); + } + } + &log($fh,"

Total unsend messages: $unsend for ".scalar(keys(%active))." active (undegraded) nodes in cluster.

\n"); + if (keys(%exclusions) > 0) { + &log($fh,"

Total incomplete updates $ignored for ".scalar(keys(%exclusions))." degraded nodes in cluster.

\n"); + } + if ($retired) { + &log($fh,"

Total unsent $nodest for $retired nodes no longer in cluster.

\n"); + } + if ($unsend > 0) { + $warnings=$warnings+$weights{'U'}*$unsend; + } } if ($unsend) { $simplestatus{'unsend'}=$unsend; } @@ -713,7 +878,11 @@ } sub finish_logging { - my ($fh,%weights)=@_; + my ($fh,$weightsref)=@_; + my %weights; + if (ref($weightsref) eq 'HASH') { + %weights = %{$weightsref}; + } &log($fh,"
\n"); $totalcount=($weights{'N'}*$notices)+($weights{'W'}*$warnings)+($weights{'E'}*$errors); &errout($fh); @@ -1086,6 +1255,62 @@ return; } +sub get_permcount_settings { + my ($domconf) = @_; + my ($defaults,$names) = &Apache::loncommon::lon_status_items(); + my (%weights,$threshold,$sysmail,$reportstatus,%exclusions); + foreach my $type ('E','W','N','U') { + $weights{$type} = $defaults->{$type}; + } + $threshold = $defaults->{'threshold'}; + $sysmail = $defaults->{'sysmail'}; + $reportstatus = 1; + if (ref($domconf) eq 'HASH') { + if (ref($domconf->{'contacts'}) eq 'HASH') { + if ($domconf->{'contacts'}{'reportstatus'} == 0) { + $reportstatus = 0; + } + if (ref($domconf->{'contacts'}{'lonstatus'}) eq 'HASH') { + if (ref($domconf->{'contacts'}{'lonstatus'}{weights}) eq 'HASH') { + foreach my $type ('E','W','N','U') { + if (exists($domconf->{'contacts'}{'lonstatus'}{weights}{$type})) { + $weights{$type} = $domconf->{'contacts'}{'lonstatus'}{weights}{$type}; + } + } + } + if (ref($domconf->{'contacts'}{'lonstatus'}{'excluded'}) eq 'ARRAY') { + my @excluded = @{$domconf->{'contacts'}{'lonstatus'}{'excluded'}}; + if (@excluded) { + map { $exclusions{$_} = 1; } @excluded; + } + } + if (exists($domconf->{'contacts'}{'lonstatus'}{'threshold'})) { + $threshold = $domconf->{'contacts'}{'lonstatus'}{'threshold'}; + } + if (exists($domconf->{'contacts'}{'lonstatus'}{'sysmail'})) { + $sysmail = $domconf->{'contacts'}{'lonstatus'}{'sysmail'}; + } + } + } + } + return ($threshold,$sysmail,$reportstatus,\%weights,\%exclusions); +} + +sub read_serverhomeIDs { + my %server; + if (-e "$perlvar{'lonTabDir'}/serverhomeIDs.tab") { + if (open(my $fh,'<',"$perlvar{'lonTabDir'}/serverhomeIDs.tab")) { + while (<$fh>) { + my($host,$id) = split(/:/); + chomp($id); + $server{$host} = $id; + } + close($fh); + } + } + return %server; +} + sub send_mail { my ($sysmail,$reportstatus) = @_; my $defdom = $perlvar{'lonDefDomain'}; @@ -1273,8 +1498,10 @@ &test_connections($fh); } if (!$justcheckdaemons && !$justcheckconnections && !$justreload) { - &check_delayed_msg($fh); - &log_simplestatus(); + my $domconf = &get_domain_config(); + my ($threshold,$sysmail,$reportstatus,$weightsref,$exclusionsref) = + &get_permcount_settings($domconf); + &check_delayed_msg($fh,$weightsref,$exclusionsref); &write_loncaparevs(); &write_serverhomeIDs(); &write_checksums(); @@ -1287,37 +1514,9 @@ &checkon_daemon($fh,'lond',40000,'USR2'); &reset_nosslverify_pids($fh,%sslrem); } - my $domconf = &get_domain_config(); - my ($defaults,$names) = &Apache::loncommon::lon_status_items(); - my (%weights,$threshold); - foreach my $type ('E','W','N') { - $weights{$type} = $defaults->{$type}; - } - my $threshold = $defaults->{'threshold'}; - my $sysmail = $defaults->{'sysmail'}; - my $reportstatus = 1; - if (ref($domconf->{'contacts'}) eq 'HASH') { - if ($domconf->{'contacts'}{'reportstatus'} == 0) { - $reportstatus = 0; - } - if (ref($domconf->{'contacts'}{'lonstatus'}) eq 'HASH') { - if (ref($domconf->{'contacts'}{'lonstatus'}{weights}) eq 'HASH') { - foreach my $type ('E','W','N') { - if (exists($domconf->{'contacts'}{'lonstatus'}{weights}{$type})) { - $weights{$type} = $domconf->{'contacts'}{'lonstatus'}{weights}{$type}; - } - } - } - } - if (exists($domconf->{'contacts'}{'lonstatus'}{'threshold'})) { - $threshold = $domconf->{'contacts'}{'lonstatus'}{'threshold'}; - } - if (exists($domconf->{'contacts'}{'lonstatus'}{'sysmail'})) { - $sysmail = $domconf->{'contacts'}{'lonstatus'}{'sysmail'}; - } - } - &finish_logging($fh,%weights); - if ($totalcount>$threshold && !$noemail) { &send_mail($sysmail,$reportstatus); } + &finish_logging($fh,$weightsref); + &log_simplestatus(); + if ($totalcount>$threshold && !$noemail) { &send_mail($sysmail,$reportstatus); } } } Index: loncom/interface/loncommon.pm diff -u loncom/interface/loncommon.pm:1.1323 loncom/interface/loncommon.pm:1.1324 --- loncom/interface/loncommon.pm:1.1323 Wed Nov 7 19:23:45 2018 +++ loncom/interface/loncommon.pm Sun Nov 18 22:50:52 2018 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # a pile of common routines # -# $Id: loncommon.pm,v 1.1323 2018/11/07 19:23:45 raeburn Exp $ +# $Id: loncommon.pm,v 1.1324 2018/11/18 22:50:52 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -16425,6 +16425,7 @@ E => 100, W => 4, N => 1, + U => 5, threshold => 200, sysmail => 2500, ); @@ -16432,6 +16433,7 @@ E => 'Errors', W => 'Warnings', N => 'Notices', + U => 'Unsent', ); return (\%defaults,\%names); } Index: loncom/interface/domainprefs.pm diff -u loncom/interface/domainprefs.pm:1.340 loncom/interface/domainprefs.pm:1.341 --- loncom/interface/domainprefs.pm:1.340 Tue Nov 6 15:37:37 2018 +++ loncom/interface/domainprefs.pm Sun Nov 18 22:50:52 2018 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Handler to set domain-wide configuration settings # -# $Id: domainprefs.pm,v 1.340 2018/11/06 15:37:37 raeburn Exp $ +# $Id: domainprefs.pm,v 1.341 2018/11/18 22:50:52 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -3581,7 +3581,7 @@ $sysmail = $defaults->{'sysmail'}; } if (ref($lonstatus{'weights'}) eq 'HASH') { - foreach my $type ('E','W','N') { + foreach my $type ('E','W','N','U') { if ($lonstatus{'weights'}{$type} =~ /^\d+$/) { $weights{$type} = $lonstatus{'weights'}{$type}; } else { @@ -3589,7 +3589,7 @@ } } } else { - foreach my $type ('E','W','N') { + foreach my $type ('E','W','N','U') { $weights{$type} = $defaults->{$type}; } } @@ -3610,7 +3610,7 @@ ''. ''.$titles->{'errorweights'}. ''; - foreach my $type ('E','W','N') { + foreach my $type ('E','W','N','U') { $datatable .= ''; @@ -13019,7 +13019,7 @@ if ((ref($contacts_hash{contacts}{lonstatus}) eq 'HASH') && (ref($contacts_hash{contacts}{lonstatus}{$key}) eq 'HASH')) { if (ref($currsetting{'lonstatus'}{$key}) eq 'HASH') { - foreach my $type ('E','W','N') { + foreach my $type ('E','W','N','U') { unless ($contacts_hash{contacts}{lonstatus}{$key}{$type} eq $currsetting{'lonstatus'}{$key}{$type}) { push(@{$changes{'lonstatus'}},$key); @@ -13027,7 +13027,7 @@ } } } else { - foreach my $type ('E','W','N') { + foreach my $type ('E','W','N','U') { if ($contacts_hash{contacts}{lonstatus}{$key}{$type} ne '') { push(@{$changes{'lonstatus'}},$key); last; @@ -13035,7 +13035,7 @@ } } } elsif (ref($currsetting{'lonstatus'}{$key}) eq 'HASH') { - foreach my $type ('E','W','N') { + foreach my $type ('E','W','N','U') { if ($currsetting{'lonstatus'}{$key}{$type} ne '') { push(@{$changes{'lonstatus'}},$key); last; @@ -13270,7 +13270,7 @@ $defval{'threshold'} = $lonstatus_defs->{'threshold'}; $defval{'sysmail'} = $lonstatus_defs->{'sysmail'}; $defval{'weights'} = - join(', ',map { $lonstatus_names->{$_}.'='.$lonstatus_defs->{$_}; } ('E','W','N')); + join(', ',map { $lonstatus_names->{$_}.'='.$lonstatus_defs->{$_}; } ('E','W','N','U')); $defval{'excluded'} = &mt('None'); if (ref($contacts_hash{'contacts'}{'lonstatus'}) eq 'HASH') { foreach my $item ('threshold','sysmail','weights','excluded') { @@ -13279,7 +13279,7 @@ $shown{$item} = $contacts_hash{'contacts'}{'lonstatus'}{$item}; } elsif ($item eq 'weights') { if (ref($contacts_hash{'contacts'}{'lonstatus'}{$item}) eq 'HASH') { - foreach my $type ('E','W','N') { + foreach my $type ('E','W','N','U') { $shown{$item} .= $lonstatus_names->{$type}.'='; if (exists($contacts_hash{'contacts'}{'lonstatus'}{$item}{$type})) { $shown{$item} .= $contacts_hash{'contacts'}{'lonstatus'}{$item}{$type};
'.$names->{$type}.'
'. '