diff --git a/opentrafficshaper/plugins/configmanager.pm b/opentrafficshaper/plugins/configmanager.pm index 5c5804d1037de934eff92c39ac77a799cf82d440..068f12ee539dca91643541c41b941864bfe76073 100644 --- a/opentrafficshaper/plugins/configmanager.pm +++ b/opentrafficshaper/plugins/configmanager.pm @@ -42,6 +42,7 @@ our (@ISA,@EXPORT,@EXPORT_OK); getLimitUsername setLimitAttribute getLimitAttribute + removeLimitAttribute getOverride getOverrides @@ -73,7 +74,7 @@ sub LIMIT_REQUIRED_ATTRIBUTES { Username IP GroupID ClassID TrafficLimitTx TrafficLimitRx - Expires Status + Status Source ) } @@ -81,8 +82,8 @@ sub LIMIT_REQUIRED_ATTRIBUTES { # Limit Changeset attributes - things that can be changed on the fly in the shaper sub LIMIT_CHANGESET_ATTRIBUTES { qw( - GroupID ClassID - TrafficLimitTx TrafficLimitRx TrafficLimitTxBurst TrafficLimitRxBurst + ClassID + TrafficLimitTx TrafficLimitRx TrafficLimitTxBurst TrafficLimitRxBurst TrafficPriority ) } @@ -98,8 +99,8 @@ sub LIMIT_PERSISTENT_ATTRIBUTES { ) } -# Mandatory override attribute, one is required -sub OVERRIDE_REQUIRED_ATTRIBUTES { +# Override match attributes, one is required +sub OVERRIDE_MATCH_ATTRIBUTES { qw( Username IP GroupID @@ -115,16 +116,25 @@ sub OVERRIDE_CHANGESET_ATTRIBUTES { # Override attributes supported for persistent storage sub OVERRIDE_PERSISTENT_ATTRIBUTES { qw( - Key - Username IP - GroupID ClassID - TrafficLimitTx TrafficLimitRx TrafficLimitTxBurst TrafficLimitRxBurst - FriendlyName Notes + FriendlyName + Username IP GroupID + ClassID TrafficLimitTx TrafficLimitRx TrafficLimitTxBurst TrafficLimitRxBurst + Notes Expires Created Source LastUpdate ) } +# Override match criteria +sub OVERRIDE_MATCH_CRITERIA { + ( + ['GroupID'], ['Username'], ['IP'], + ['GroupID','Username'], ['GroupID','IP'], + ['Username','IP'], + ['GroupID','Username','IP'], + ) +} + # Plugin info @@ -161,8 +171,6 @@ my $config = { }; -# Pending changes -my $changeQueue = { }; # # LIMITS @@ -197,6 +205,7 @@ my $changeQueue = { }; # - unknown # * Source # - This is the source of the limit, typically plugin.ModuleName +my $limitChangeQueue = { }; my $limits = { }; my $limitIPMap = { }; my $limitIDMap = { }; @@ -234,7 +243,11 @@ my $limitIDCounter = 1; # - Notes on this limit # * Source # - This is the source of the limit, typically plugin.ModuleName +my $overrideChangeQueue = { }; my $overrides = { }; +my $overrideMap = { }; +my $overrideIDMap = { }; +my $overrideIDCounter = 1; # Initialize plugin @@ -337,7 +350,10 @@ sub plugin_init tick => \&session_tick, process_limit_change => \&process_limit_change, + process_limit_remove => \&process_limit_remove, + process_override_change => \&process_override_change, + process_override_remove => \&process_override_remove, handle_SIGHUP => \&handle_SIGHUP, } @@ -348,7 +364,7 @@ sub plugin_init # Start the plugin sub plugin_start { - $logger->log(LOG_INFO,"[CONFIGMANAGER] Started with ".( keys %{$changeQueue} )." queued items"); + $logger->log(LOG_INFO,"[CONFIGMANAGER] Started with ".( keys %{$limitChangeQueue} )." queued items"); } @@ -390,12 +406,17 @@ sub session_stop # Blow away all data $globals = undef; - $changeQueue = { }; + $limitChangeQueue = { }; $limits = { }; $limitIPMap = { }; $limitIDMap = { }; $limitIDCounter = 1; + + $overrideChangeQueue = { }; $overrides = { }; + $overrideMap = { }; + $overrideIDMap = { }; + $overrideIDCounter = 1; # XXX: Blow away rest? config? $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Shutdown"); @@ -409,6 +430,7 @@ sub session_tick { my $kernel = $_[KERNEL]; + _process_override_change_queue($kernel); _process_limit_change_queue($kernel); # Reset tick @@ -423,6 +445,14 @@ sub process_limit_change _process_limit_change($limit); } +# Process limit remove +sub process_limit_remove +{ + my ($kernel, $limit) = @_[KERNEL, ARG0]; + + _process_limit_remove($kernel,$limit); +} + # Process override change sub process_override_change { @@ -431,6 +461,15 @@ sub process_override_change _process_override_change($override); } +# Process override remove +sub process_override_remove +{ + my ($kernel, $override) = @_[KERNEL, ARG0]; + + _process_override_remove($override); + _resolve_overrides_and_post($kernel); +} + # Function to check the group ID exists sub checkGroupID @@ -439,7 +478,7 @@ sub checkGroupID if (defined($config->{'groups'}->{$gid})) { return $gid; } - return; + return undef; } @@ -450,7 +489,7 @@ sub checkClassID if (defined($config->{'classes'}->{$cid})) { return $cid; } - return; + return undef; } @@ -461,7 +500,7 @@ sub checkStatus if ($status eq "new" || $status eq "offline" || $status eq "online" || $status eq "conflict" || $status eq "unknown") { return $status } - return; + return undef; } @@ -472,7 +511,7 @@ sub getLimitUsername if (defined($limits->{$lid})) { return $limits->{$lid}->{'Username'}; } - return; + return undef; } @@ -485,7 +524,7 @@ sub getLimit my %limit = %{$limits->{$lid}}; return \%limit; } - return; + return undef; } @@ -503,10 +542,9 @@ sub setLimitAttribute # Only set it if it exists - if (defined($limits->{$lid})) { + if (defined($lid) && defined($limits->{$lid})) { $limits->{$lid}->{'attributes'}->{$attr} = $value; } - return; } @@ -517,10 +555,23 @@ sub getLimitAttribute # Check if attribute exists first - if (defined($limits->{$lid}) && defined($limits->{$lid}->{'attributes'}) && defined($limits->{$lid}->{'attributes'}->{$attr})) { + if (defined($lid) && defined($limits->{$lid}) && defined($limits->{$lid}->{'attributes'}) && defined($limits->{$lid}->{'attributes'}->{$attr})) { return $limits->{$lid}->{'attributes'}->{$attr}; } - return; + return undef; +} + + +# Function to remove a limit attribute +sub removeLimitAttribute +{ + my ($lid,$attr) = @_; + + + # Check if attribute exists first + if (defined($lid) && defined($limits->{$lid}) && defined($limits->{$lid}->{'attributes'}) && defined($limits->{$lid}->{'attributes'}->{$attr})) { + delete($limits->{$lid}->{'attributes'}->{$attr}); + } } @@ -529,11 +580,11 @@ sub getOverride { my $oid = shift; - if (defined($overrides->{$oid})) { + if (defined($oid) && defined($overrides->{$oid})) { my %override = %{$overrides->{$oid}}; return \%override; } - return; + return undef; } @@ -549,9 +600,10 @@ sub setShaperState { my ($lid,$state) = @_; - if (defined($limits->{$lid})) { + if (defined($lid) && defined($limits->{$lid})) { $limits->{$lid}->{'_shaper.state'} = $state; } + return undef; } @@ -559,10 +611,10 @@ sub setShaperState sub getShaperState { my $lid = shift; - if (defined($limits->{$lid})) { + if (defined($lid) && defined($limits->{$lid})) { return $limits->{$lid}->{'_shaper.state'}; } - return; + return undef; } @@ -579,7 +631,10 @@ sub getTrafficClasses sub getTrafficClassName { my $class = shift; - return $config->{'classes'}->{$class}; + if (defined($class) && defined($config->{'classes'}->{$class})) { + return $config->{'classes'}->{$class}; + } + return undef; } @@ -587,10 +642,10 @@ sub getTrafficClassName sub isTrafficClassValid { my $class = shift; - if (defined($config->{'classes'}->{$class})) { + if (defined($class) && defined($config->{'classes'}->{$class})) { return $class; } - return; + return undef; } @@ -608,8 +663,8 @@ sub handle_SIGHUP # Internal functions # -# Function to compute the changes between two users -sub _getLimitChangeset +# Function to compute the changes between two limits +sub _getAppliedLimitChangeset { my ($orig,$new) = @_; @@ -617,13 +672,67 @@ sub _getLimitChangeset # Loop through what can change foreach my $item (LIMIT_CHANGESET_ATTRIBUTES) { - # Check if its first set, if it is, check if its changed - if (defined($new->{$item}) && $orig->{$item} ne $new->{$item}) { + # If new item is defined, and we didn't have a value before, or the new value is different + if (defined($new->{$item}) && (!defined($orig->{$item}) || $orig->{$item} ne $new->{$item})) { # If so record it & make the change $res->{$item} = $orig->{$item} = $new->{$item}; } } + # If we have an override changeset to calculate, lets do it! + if (defined(my $overrideNew = $orig->{'override'})) { + + # If there is currently a live override + if (defined(my $overrideLive = $orig->{'_override.live'})) { + + # Loop through everything + foreach my $item (LIMIT_CHANGESET_ATTRIBUTES) { + # If we have a new override defined + if (defined($overrideNew->{$item})) { + # Check if it differs + if ($overrideLive->{$item} ne $overrideNew->{$item}) { + $res->{$item} = $overrideLive->{$item} = $overrideNew->{$item}; + } + + # We don't have a new override, but we had one before + } elsif (defined($overrideLive->{$item})) { + # If it differs to the main item, then add it to the change list + if ($overrideLive->{$item} ne $orig->{$item}) { + $res->{$item} = $orig->{$item}; + } + + # Its no longer live so remove it + delete($overrideLive->{$item}); + } + } + + # If there was nothing being overridden and is now... + } else { + # Loop and add + foreach my $item (keys %{$overrideNew}) { + # If they differ change it + if ($orig->{$item} ne $overrideNew->{$item}) { + $res->{$item} = $overrideLive->{$item} = $overrideNew->{$item}; + } + } + $orig->{'_override.live'} = $overrideLive; + } + + + # If there is no new override... + } else { + # Only if there was indeed one before... + if (defined(my $overrideLive = $orig->{'_override.live'})) { + # Make sure we set all the values back + foreach my $item (keys %{$overrideLive}) { + $res->{$item} = $orig->{$item}; + } + # Blow the override away + delete($orig->{'_override.live'}); + } + } + + return $res; } @@ -643,7 +752,7 @@ sub _process_limit_change } } if ($isInvalid) { - $logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process limit change as not attributes is missing: '$isInvalid'"); + $logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process limit change as thre is an attribute missing: '$isInvalid'"); return; } @@ -686,7 +795,7 @@ sub _process_limit_change $limitChange->{'TrafficPriority'} = defined($limit->{'TrafficPriority'}) ? $limit->{'TrafficPriority'} : 5; # Set when this entry expires - $limitChange->{'Expires'} = defined($limit->{'Expires'}) ? $limit->{'Expires'} : 0; + $limitChange->{'Expires'} = defined($limit->{'Expires'}) ? int($limit->{'Expires'}) : 0; # Set friendly name and notes $limitChange->{'FriendlyName'} = $limit->{'FriendlyName'}; @@ -704,19 +813,61 @@ sub _process_limit_change $limitChange->{'Source'} = $limit->{'Source'}; # Create a unique limit identifier - my $limitUniq = $limit->{'Username'} . "/" . $limit->{'IP'}; + my $limitID = sprintf('%s/%s',$limit->{'Username'},$limit->{'IP'}); # If we've not seen it my $lid; - if (!defined($lid = $limitIDMap->{$limitUniq})) { - # Give it the next limitID in the list - $limitIDMap->{$limitUniq} = $lid = ++$limitIDCounter; + if (!defined($lid = $limitIDMap->{$limitID})) { + # Give it the next limit ID in the list + $limitIDMap->{$limitID} = $lid = ++$limitIDCounter; } # Set the user ID before we post to the change queue $limitChange->{'ID'} = $lid; $limitChange->{'LastUpdate'} = time(); # Push change to change queue - $changeQueue->{$lid} = $limitChange; + $limitChangeQueue->{$lid} = $limitChange; +} + + +# Process actual limit removal +sub _process_limit_remove +{ + my ($kernel,$limit) = @_; + + my $lid = $limit->{'ID'}; + + + # If the entry is not live, remove it + if ($limit->{'_shaper.state'} == SHAPER_NOTLIVE || $limit->{'_shaper.state'} == SHAPER_PENDING) { + # Remove from system + delete($limits->{$lid}); + # Set this UID as no longer using this IP + # NK: If we try remove it before the limit is actually removed we could get a reconnection causing this value + # to be totally gone, which means we not tracking this limit using this IP anymore, not easily solved!! + delete($limitIPMap->{$limit->{'IP'}}->{$lid}); + # Check if we can delete the IP too + if (keys %{$limitIPMap->{$limit->{'IP'}}} == 0) { + delete($limitIPMap->{$limit->{'IP'}}); + } + + # Remove from change queue + delete($limitChangeQueue->{$lid}); + + # If the entry is live, schedule shaper removal + } elsif ($limit->{'_shaper.state'} == SHAPER_LIVE) { + # Build a removal... + my $rlimit = { + 'Username' => $limit->{'Username'}, + 'IP' => $limit->{'IP'}, + 'Status' => 'offline', + 'LastUpdate' => time(), + }; + # Queue removal + $limitChangeQueue->{$lid} = $rlimit; + + # Post removal to shaper + $kernel->post('shaper' => 'remove' => $lid); + } } @@ -729,7 +880,7 @@ sub _process_override_change # Pull in mandatory items and check if the result is valid my $overrideChange; my $isValid = 0; - foreach my $item (OVERRIDE_REQUIRED_ATTRIBUTES) { + foreach my $item (OVERRIDE_MATCH_ATTRIBUTES) { $overrideChange->{$item} = $override->{$item}; $isValid++; } @@ -761,7 +912,7 @@ sub _process_override_change } # Set when this entry expires - $overrideChange->{'Expires'} = defined($override->{'Expires'}) ? $override->{'Expires'} : 0; + $overrideChange->{'Expires'} = defined($override->{'Expires'}) ? int($override->{'Expires'}) : 0; # Set friendly name and notes $overrideChange->{'FriendlyName'} = $override->{'FriendlyName'}; @@ -772,16 +923,39 @@ sub _process_override_change $overrideChange->{'LastUpdate'} = time(); - # This is our key for this entry - my $oid = sprintf('%s%%%s%%%s', - defined($overrideChange->{'Username'}) ? $overrideChange->{'Username'} : "", - defined($overrideChange->{'IP'}) ? $overrideChange->{'IP'} : "", - defined($overrideChange->{'GroupID'}) ? $overrideChange->{'GroupID'} : "" - ); - # Set the user ID before we post to the change queue - $overrideChange->{'Key'} = $oid; + # Create a unique override identifier, FriendlyName is unique + my $overrideID = $override->{'FriendlyName'}; + # If we've not seen it + my $oid; + if (!defined($oid = $overrideIDMap->{$overrideID})) { + # Give it the next override ID in the list + $overrideIDMap->{$overrideID} = $oid = ++$overrideIDCounter; + } + $overrideChange->{'ID'} = $oid; + + $overrideChangeQueue->{$oid} = $overrideChange; +} + + +# Process actual override removal +sub _process_override_remove +{ + my $override = shift; + my $oid = $override->{'ID'}; - $overrides->{$oid} = $overrideChange; + + # Remove from system + delete($overrides->{$oid}); + # Remove from change queue + delete($overrideChangeQueue->{$oid}); + # Remove from map + delete($overrideMap->{$override->{'GroupID'}}->{$override->{'Username'}}->{$override->{'IP'}}); + if (keys %{$overrideMap->{$override->{'GroupID'}}->{$override->{'Username'}}} < 1) { + delete($overrideMap->{$override->{'GroupID'}}->{$override->{'Username'}}); + } + if (keys %{$overrideMap->{$override->{'GroupID'}}} < 1) { + delete($overrideMap->{$override->{'GroupID'}}); + } } @@ -815,23 +989,18 @@ sub _load_statefile my $override = $stateHash{$section}; # Our user override - my $ouser; + my $coverride; foreach my $attr (OVERRIDE_PERSISTENT_ATTRIBUTES) { if (defined($override->{$attr})) { - $ouser->{$attr} = $override->{$attr}; + $coverride->{$attr} = $override->{$attr}; } } - # Check username, IP or gorup ID is defined - if (!defined($ouser->{'Key'})) { - $logger->log(LOG_WARN,"[CONFIGMANAGER] Failed to load override with no Key '$section'"); - next; - } - - $overrides->{$ouser->{'Key'}} = $ouser; + # Proces this override + _process_override_change($coverride); } - # Loop with persistent users + # Loop with persistent limits foreach my $section ($state->GroupMembers('persist')) { my $user = $stateHash{$section}; @@ -866,45 +1035,45 @@ sub _write_statefile return; } - # Only write out if we actually have users, else we may of crashed? - if (keys %{$limits} < 1) { - $logger->log(LOG_WARN,"[CONFIGMANAGER] Not writing state file as there are no active users"); + # Only write out if we actually have limits & overrides, else we may of crashed? + if (keys %{$limits} < 1 && keys %{$overrides} < 1) { + $logger->log(LOG_WARN,"[CONFIGMANAGER] Not writing state file as there are no active limits or overrides"); return; } # Create new state file object my $state = new Config::IniFiles(); - # Loop with persistent users, these are users with expires = 0 - foreach my $lid (keys %{$limits}) { + # Loop with persistent limits, these are limits with expires = 0 + while ((undef, my $limit) = each(%{$limits})) { # Skip over expiring entries, we only want persistent ones # XXX: Should we not just save all of them? load? - next if ($limits->{$lid}->{'Expires'}); + next if ($limit->{'Expires'}); # Pull in the section name - my $section = "persist " . $limits->{$lid}->{'Username'}; + my $section = "persist " . $limit->{'Username'}; # Add a new section for this user $state->AddSection($section); # Items we want for persistent entries foreach my $pItem (LIMIT_PERSISTENT_ATTRIBUTES) { # Set items up - if (defined(my $value = $limits->{$lid}->{$pItem})) { + if (defined(my $value = $limit->{$pItem})) { $state->newval($section,$pItem,$value); } } } # Loop with overrides - foreach my $username (keys %{$overrides}) { + foreach my $oid (keys %{$overrides}) { # Pull in the section name - my $section = "override " . $username; + my $section = "override " . $oid; # Add a new section for this user $state->AddSection($section); # Items we want for override entries foreach my $pItem (OVERRIDE_PERSISTENT_ATTRIBUTES) { # Set items up - if (defined(my $value = $overrides->{$username}->{$pItem})) { + if (defined(my $value = $overrides->{$oid}->{$pItem})) { $state->newval($section,$pItem,$value); } } @@ -921,7 +1090,7 @@ sub _write_statefile } -# Do the actual queue processing +# Do the actual limit queue processing sub _process_limit_change_queue { my $kernel = shift; @@ -930,49 +1099,37 @@ sub _process_limit_change_queue # Now my $now = time(); - # - # LOOP WITH CHANGES - # - - foreach my $lid (keys %{$changeQueue}) { + # Loop with changes in queue + foreach my $lid (keys %{$limitChangeQueue}) { # Changes for limit # Minimum required info is: # - Username # - IP # - Status # - LastUpdate - my $climit = $changeQueue->{$lid}; + my $climit = $limitChangeQueue->{$lid}; # # LIMIT IN LIST # if (defined(my $glimit = $limits->{$lid})) { + my $updateShaper = 0; # This is a new limit notification if ($climit->{'Status'} eq "new") { $logger->log(LOG_INFO,"[CONFIGMANAGER] Limit '$climit->{'Username'}' [$lid], limit already live but new state provided?"); - - # Get the changes we made and push them to the shaper - if (my $changes = _getLimitChangeset($glimit,$climit)) { - # Post to shaper - $kernel->post("shaper" => "change" => $lid => $changes); - } + $updateShaper = 1; # Remove from change queue - delete($changeQueue->{$lid}); + delete($limitChangeQueue->{$lid}); # Online or "ping" status notification } elsif ($climit->{'Status'} eq "online") { $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Limit '$climit->{'Username'}' [$lid], limit still online"); - - # Get the changes we made and push them to the shaper - if (my $changes = _getLimitChangeset($glimit,$climit)) { - # Post to shaper - $kernel->post("shaper" => "change" => $lid => $changes); - } + $updateShaper = 1; # Remove from change queue - delete($changeQueue->{$lid}); + delete($limitChangeQueue->{$lid}); # Offline notification, this we going to treat specially } elsif ($climit->{'Status'} eq "offline") { @@ -986,18 +1143,7 @@ sub _process_limit_change_queue if ($glimit->{'_shaper.state'} == SHAPER_NOTLIVE) { $logger->log(LOG_INFO,"[CONFIGMANAGER] Limit '$climit->{'Username'}' [$lid] offline and removed from shaper"); - # Remove from system - delete($limits->{$lid}); - # Remove from change queue - delete($changeQueue->{$lid}); - # Set this UID as no longer using this IP - # NK: If we try remove it before the limit is actually removed we could get a reconnection causing this value - # to be totally gone, which means we not tracking this limit using this IP anymore, not easily solved!! - delete($limitIPMap->{$glimit->{'IP'}}->{$lid}); - # Check if we can delete the IP too - if (keys %{$limitIPMap->{$glimit->{'IP'}}} == 0) { - delete($limitIPMap->{$glimit->{'IP'}}); - } + _process_limit_remove($kernel,$glimit); # Next record, we don't want to do any updates below next; @@ -1006,8 +1152,7 @@ sub _process_limit_change_queue } elsif ($glimit->{'_shaper.state'} == SHAPER_LIVE) { $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Limit '$climit->{'Username'}' [$lid] offline, queue remove from shaper"); - # Post removal to shaper - $kernel->post("shaper" => "remove" => $lid); + _process_limit_remove($kernel,$glimit); } else { $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Limit '$climit->{'Username'}' [$lid], limit in list, but offline now and". @@ -1019,9 +1164,11 @@ sub _process_limit_change_queue # Update the limit data $glimit->{'Status'} = $climit->{'Status'}; $glimit->{'LastUpdate'} = $climit->{'LastUpdate'}; - $glimit->{'Expires'} = $climit->{'Expires'}; # Set these if they exist + if (defined($climit->{'Expires'})) { + $glimit->{'Expires'} = $climit->{'Expires'}; + } if (defined($climit->{'FriendlyName'})) { $glimit->{'FriendlyName'} = $climit->{'FriendlyName'}; } @@ -1029,14 +1176,30 @@ sub _process_limit_change_queue $glimit->{'Notes'} = $climit->{'Notes'}; } - # - # LIMIT NOT IN LIST - # + # If the group changed, re-apply the overrides + # Note: This MUST be done here BEFORE the changeset, so we post the shaper changes after the overrides take effect + if (defined($climit->{'GroupID'}) && $climit->{'GroupID'} != $glimit->{'GroupID'}) { + _resolve_overrides($lid); + $updateShaper = 1; + } + # If we need to post a shaper update, its time to calculate the changeset + if ($updateShaper) { + # Generate a changeset + if (my $changeset = _getAppliedLimitChangeset($glimit,$climit)) { + # Post it + $kernel->post('shaper' => 'change' => $lid => $changeset); + } + } + + + # Limit is not in the global list, must be an addition? } else { # We take new and online notifications the same way here if the limit is not in our global limit list already if (($climit->{'Status'} eq "new" || $climit->{'Status'} eq "online")) { $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Processing new limit '$climit->{'Username'}' [$lid]"); + my $updateShaper = 0; + # We first going to look for IP conflicts... my @ipLimits = keys %{$limitIPMap->{$climit->{'IP'}}}; if ( @@ -1054,7 +1217,7 @@ sub _process_limit_change_queue push(@conflictUsernames,$limits->{$lid}->{'Username'}); } # Output log line - $logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process limit '".$climit->{'Username'}."' IP '$climit->{'IP'}' conflicts with users '". + $logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process limit '".$climit->{'Username'}."' IP '$climit->{'IP'}' conflicts with limits '". join(',',@conflictUsernames)."'."); # We cannot trust shaping when there is more than 1 limit on the IP, so we going to remove all limits with this @@ -1067,8 +1230,8 @@ sub _process_limit_change_queue # If the limit is active or pending on the shaper, remove it if ($glimit2->{'_shaper.state'} == SHAPER_LIVE || $glimit2->{'_shaper.state'} == SHAPER_PENDING) { $logger->log(LOG_WARN,"[CONFIGMANAGER] Removing conflicted limit '".$glimit2->{'Username'}."' [$lid2] from shaper'"); - # Post removal to shaper - $kernel->post("shaper" => "remove" => $lid2); + # Post removal to shaper, these are already in the shaper, so this is why we do it here and not after the overrides below + $kernel->post('shaper' => 'remove' => $lid2); # Update that we're offline directly to global limit table $glimit2->{'Status'} = 'conflict'; } @@ -1079,8 +1242,7 @@ sub _process_limit_change_queue } else { # Post to the limit to the shaper $climit->{'_shaper.state'} = SHAPER_PENDING; - $kernel->post("shaper" => "add" => $lid); - + $updateShaper = 1; } # Set this UID as using this IP @@ -1089,6 +1251,21 @@ sub _process_limit_change_queue # This is now live $limits->{$lid} = $climit; + # Resolve this limit's overrides, this works on the GLOBAL $limits!! + _resolve_overrides($lid); + + # Just to keep things in the right order + if ($updateShaper) { + # We need a hack to blank the current shaping items so we can generate an initial changeset below + # dlimit - all attrs, climit - our own limit, with attrs removed + my $dlimit; %{$dlimit} = %{$climit}; + foreach my $item (LIMIT_CHANGESET_ATTRIBUTES) { + delete($climit->{$item}); + } + my $changeset = _getAppliedLimitChangeset($climit,$dlimit); + $kernel->post('shaper' => 'add' => $lid => $changeset); + } + # Limit is not in our list and this is an unknown state we're trasitioning to } else { $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Ignoring limit '$climit->{'Username'}' [$lid] state '$climit->{'Status'}', not in our". @@ -1096,7 +1273,7 @@ sub _process_limit_change_queue } # Remove from change queue - delete($changeQueue->{$lid}); + delete($limitChangeQueue->{$lid}); } } @@ -1105,10 +1282,8 @@ sub _process_limit_change_queue # # CHECK OUT CONNECTED LIMITS # - foreach my $lid (keys %{$limits}) { - # Global limit - my $glimit = $limits->{$lid}; - +#FIXME: CLEANUP FUNCTION FOR THIS, RUN EVERY 5 MINS? + while ((my $lid, my $glimit) = each(%{$limits})) { # Check for expired limits if ($glimit->{'Expires'} && $glimit->{'Expires'} < $now) { $logger->log(LOG_INFO,"[CONFIGMANAGER] Limit '$glimit->{'Username'}' has expired, marking offline"); @@ -1120,9 +1295,151 @@ sub _process_limit_change_queue 'LastUpdate' => $glimit->{'LastUpdate'}, }; # Add to change queue - $changeQueue->{$lid} = $climit; + $limitChangeQueue->{$lid} = $climit; + } + } +} + + +# Do the actual override queue processing +sub _process_override_change_queue +{ + my $kernel = shift; + + + # Now + my $now = time(); + + # Overrides changed + my $overridesChanged = 0; + + # Loop with override change queue + foreach my $oid (keys %{$overrideChangeQueue}) { + my $coverride = $overrideChangeQueue->{$oid}; + + # Blank attributes not specified + foreach my $attr (OVERRIDE_MATCH_ATTRIBUTES) { + if (!defined($coverride->{$attr})) { + $coverride->{$attr} = ''; + } + } + + # This is now live + $overrides->{$oid} = $coverride; + $overrideMap->{$coverride->{'GroupID'}}->{$coverride->{'Username'}}->{$coverride->{'IP'}} = $coverride; + + # Remove from change queue + delete($overrideChangeQueue->{$oid}); + + $overridesChanged = 1; + } + +#FIXME: CLEANUP FUNCTION FOR THIS, RUN EVERY 5 MINS? + # Check for expired overides + while ((undef, my $goverride) = each(%{$overrides})) { + # Check for expired overrides + if ($goverride->{'Expires'} && $goverride->{'Expires'} < $now) { + $logger->log(LOG_INFO,"[CONFIGMANAGER] Override 'Username: %s, IP: %s' has expired, removing", + $goverride->{'Username'}, + $goverride->{'IP'}, + ); + + # Remove the override + _process_override_remove($goverride); + + $overridesChanged = 1; + } + + } + + # If something changed, resolve overrides again + # XXX: maybe we can be more efficient here? + if ($overridesChanged) { + _resolve_overrides_and_post($kernel); + } +} + + +# This function calls _resolve_overrides() plus posts events to the shaper +sub _resolve_overrides_and_post +{ + my ($kernel,$lid) = @_; + + + # Check if any items actually were changed + my @changed; + if (!(@changed = _resolve_overrides())) { + return; + } + + # If so generate a changeset, with no limit change (resolves overrides internally) and post it + foreach my $lid (@changed) { + # Get our changeset, with no limit applied to it + if (my $changeset = _getAppliedLimitChangeset($limits->{$lid},{})) { + # Post to shaper + $kernel->post('shaper' => 'change' => $lid => $changeset); + } + } +} + + +# Resolve all overrides and post limit changes if any match +# We take 1 optional argument, which is a single limit to process +sub _resolve_overrides +{ + my $lid = shift; + + + # Hack to intercept and create a single element hash + my $limitHash; + if (defined($lid)) { + $limitHash->{$lid} = $limits->{$lid}; + } else { + $limitHash = $limits; + } + + # Loop with all limits, keep a list of lid's updated + my @overridden; + while ((my $lid, my $limit) = each(%{$limitHash})) { + my $overrideResult; + + # Loop with the attributes in matching order + foreach my $attrSet (OVERRIDE_MATCH_CRITERIA) { + # Start with a blank match + my $criteria = { 'GroupID' => '', 'Username' => '', 'IP' => '' }; + + # Build match from user + foreach my $attr (@{$attrSet}) { + if ($attr ne '') { + $criteria->{$attr} = $limit->{$attr}; + } + } + + # Check for match + if ( + defined($overrideMap->{$criteria->{'GroupID'}}) && defined($overrideMap->{$criteria->{'GroupID'}}->{$criteria->{'Username'}}) && + defined(my $moverride = $overrideMap->{$criteria->{'GroupID'}}->{$criteria->{'Username'}}->{$criteria->{'IP'}}) + ) { + # Apply attributes to override result + foreach my $attr (OVERRIDE_CHANGESET_ATTRIBUTES) { + # Merge in attribute if the matched override has it set + if (defined($moverride->{$attr})) { + $overrideResult->{$attr} = $moverride->{$attr}; + } + } + } + } + + # if we have an override result, it means we matched something + # If we don't have an overrideResult, and we had one, it means something changed too + if (defined($overrideResult) || defined($limit->{'override'})) { + push(@overridden,$lid); } + + $limit->{'override'} = $overrideResult; } + + return @overridden; }