From 42c729c1deb77bec74825ec8f43f3e0d75f8f882 Mon Sep 17 00:00:00 2001 From: Robert Anderson <randerson@lbsd.net> Date: Mon, 24 Aug 2009 12:14:12 +0000 Subject: [PATCH] Fixed accounting start, update and stop records handling sessions spanning over multiple periods Added UPGRADING file for upgrading information from previous revision --- UPGRADING | 124 ++++++ smradius/constants.pm | 14 +- .../modules/accounting/mod_accounting_sql.pm | 411 ++++++++++++++---- .../modules/features/mod_feature_capping.pm | 5 +- .../modules/system/mod_config_sql_topups.pm | 25 +- smradiusd.conf | 70 ++- 6 files changed, 544 insertions(+), 105 deletions(-) create mode 100644 UPGRADING diff --git a/UPGRADING b/UPGRADING new file mode 100644 index 00000000..55e6707e --- /dev/null +++ b/UPGRADING @@ -0,0 +1,124 @@ +r509: + # Database + + ALTER TABLE accounting ADD PeriodKey VARCHAR(7); + ALTER TABLE accounting_summary DROP COLUMN AcctInputPackets; + + # smradiusd.conf + + Modified: + accounting_start_query=<<EOT + INSERT INTO + @TP@accounting + ( + Username, + ServiceType, + FramedProtocol, + NASPort, + NASPortType, + CallingStationID, + CalledStationID, + NASPortID, + AcctSessionID, + FramedIPAddress, + AcctAuthentic, + EventTimestamp, + AcctStatusType, + NASIdentifier, + NASIPAddress, + AcctDelayTime, + PeriodKey + ) + VALUES + ( + %{request.User-Name}, + %{request.Service-Type}, + %{request.Framed-Protocol}, + %{request.NAS-Port}, + %{request.NAS-Port-Type}, + %{request.Calling-Station-Id}, + %{request.Called-Station-Id}, + %{request.NAS-Port-Id}, + %{request.Acct-Session-Id}, + %{request.Framed-IP-Address}, + %{request.Acct-Authentic}, + %{request.Timestamp}, + %{request.Acct-Status-Type}, + %{request.NAS-Identifier}, + %{request.NAS-IP-Address}, + %{request.Acct-Delay-Time}, + %{query.PeriodKey} + ) + EOT + + accounting_update_query=<<EOT + UPDATE + @TP@accounting + SET + AcctSessionTime = %{query.AcctSessionTime}, + AcctInputOctets = %{query.AcctInputOctets}, + AcctInputGigawords = %{query.AcctInputGigawords}, + AcctInputPackets = %{query.AcctInputPackets}, + AcctOutputOctets = %{query.AcctOutputOctets}, + AcctOutputGigawords = %{query.AcctOutputGigawords}, + AcctOutputPackets = %{query.AcctOutputPackets}, + AcctStatusType = %{request.Acct-Status-Type} + WHERE + Username = %{request.User-Name} + AND AcctSessionID = %{request.Acct-Session-Id} + AND NASIPAddress = %{request.NAS-IP-Address} + AND PeriodKey = %{query.PeriodKey} + EOT + + accounting_stop_query=<<EOT + UPDATE + @TP@accounting + SET + AcctSessionTime = %{query.Acct-Session-Time}, + AcctInputOctets = %{query.Acct-Input-Octets}, + AcctInputGigawords = %{query.Acct-Input-Gigawords}, + AcctInputPackets = %{query.Acct-Input-Packets}, + AcctOutputOctets = %{query.Acct-Output-Octets}, + AcctOutputGigawords = %{query.Acct-Output-Gigawords}, + AcctOutputPackets = %{query.Acct-Output-Packets} + WHERE + Username = %{request.User-Name} + AND AcctSessionID = %{request.Acct-Session-Id} + AND NASIPAddress = %{request.NAS-IP-Address} + AND PeriodKey = %{query.PeriodKey} + EOT + + Added: + accounting_update_get_records_query=<<EOT + SELECT + SUM(AcctInputOctets) AS InputOctets, + SUM(AcctInputPackets) AS InputPackets, + SUM(AcctOutputOctets) AS OutputOctets, + SUM(AcctOutputPackets) AS OutputPackets, + SUM(AcctInputGigawords) AS InputGigawords, + SUM(AcctOutputGigawords) AS OutputGigawords, + SUM(AcctSessionTime) AS SessionTime, + PeriodKey + FROM + @TP@accounting + WHERE + Username = %{request.User-Name} + AND AcctSessionID = %{request.Acct-Session-Id} + AND NASIPAddress = %{request.NAS-IP-Address} + GROUP BY + PeriodKey + ORDER BY + ID ASC + EOT + + accounting_stop_status_query=<<EOT + UPDATE + @TP@accounting + SET + AcctStatusType = %{request.Acct-Status-Type}, + AcctTerminateCause = %{request.Acct-Terminate-Cause} + WHERE + Username = %{request.User-Name} + AND AcctSessionID = %{request.Acct-Session-Id} + AND NASIPAddress = %{request.NAS-IP-Address} + EOT diff --git a/smradius/constants.pm b/smradius/constants.pm index 9a4a6e40..1e454161 100644 --- a/smradius/constants.pm +++ b/smradius/constants.pm @@ -34,17 +34,21 @@ our (@ISA,@EXPORT,@EXPORT_OK); MOD_RES_ACK MOD_RES_NACK MOD_RES_SKIP + + UINT_MAX ); @EXPORT_OK = (); use constant { - RES_OK => 0, - RES_ERROR => -1, + RES_OK => 0, + RES_ERROR => -1, - MOD_RES_SKIP => 0, - MOD_RES_ACK => 1, - MOD_RES_NACK => 2, + MOD_RES_SKIP => 0, + MOD_RES_ACK => 1, + MOD_RES_NACK => 2, + + UINT_MAX => 2**32 }; diff --git a/smradius/modules/accounting/mod_accounting_sql.pm b/smradius/modules/accounting/mod_accounting_sql.pm index 6469cf14..1e19f891 100644 --- a/smradius/modules/accounting/mod_accounting_sql.pm +++ b/smradius/modules/accounting/mod_accounting_sql.pm @@ -28,6 +28,7 @@ use smradius::util; use POSIX qw(ceil); use DateTime; +use Math::BigInt; # Exporter stuff @@ -94,7 +95,8 @@ sub init AcctStatusType, NASIdentifier, NASIPAddress, - AcctDelayTime + AcctDelayTime, + PeriodKey ) VALUES ( @@ -113,39 +115,74 @@ sub init %{request.Acct-Status-Type}, %{request.NAS-Identifier}, %{request.NAS-IP-Address}, - %{request.Acct-Delay-Time} + %{request.Acct-Delay-Time}, + %{query.PeriodKey} ) '; + $config->{'accounting_update_get_records_query'} = ' + SELECT + SUM(AcctInputOctets) AS InputOctets, + SUM(AcctInputPackets) AS InputPackets, + SUM(AcctOutputOctets) AS OutputOctets, + SUM(AcctOutputPackets) AS OutputPackets, + SUM(AcctInputGigawords) AS InputGigawords, + SUM(AcctOutputGigawords) AS OutputGigawords, + SUM(AcctSessionTime) AS SessionTime, + PeriodKey + FROM + @TP@accounting + WHERE + Username = %{request.User-Name} + AND AcctSessionID = %{request.Acct-Session-Id} + AND NASIPAddress = %{request.NAS-IP-Address} + GROUP BY + PeriodKey + ORDER BY + ID ASC + '; + $config->{'accounting_update_query'} = ' UPDATE @TP@accounting SET - AcctSessionTime = %{request.Acct-Session-Time}, - AcctInputOctets = %{request.Acct-Input-Octets}, - AcctInputGigawords = %{request.Acct-Input-Gigawords}, - AcctInputPackets = %{request.Acct-Input-Packets}, - AcctOutputOctets = %{request.Acct-Output-Octets}, - AcctOutputGigawords = %{request.Acct-Output-Gigawords}, - AcctOutputPackets = %{request.Acct-Output-Packets}, + AcctSessionTime = %{query.SessionTime}, + AcctInputOctets = %{query.InputOctets}, + AcctInputGigawords = %{query.InputGigawords}, + AcctInputPackets = %{query.InputPackets}, + AcctOutputOctets = %{query.OutputOctets}, + AcctOutputGigawords = %{query.OutputGigawords}, + AcctOutputPackets = %{query.OutputPackets}, AcctStatusType = %{request.Acct-Status-Type} WHERE Username = %{request.User-Name} AND AcctSessionID = %{request.Acct-Session-Id} AND NASIPAddress = %{request.NAS-IP-Address} + AND PeriodKey = %{query.PeriodKey} '; $config->{'accounting_stop_query'} = ' UPDATE @TP@accounting SET - AcctSessionTime = %{request.Acct-Session-Time}, - AcctInputOctets = %{request.Acct-Input-Octets}, - AcctInputGigawords = %{request.Acct-Input-Gigawords}, - AcctInputPackets = %{request.Acct-Input-Packets}, - AcctOutputOctets = %{request.Acct-Output-Octets}, - AcctOutputGigawords = %{request.Acct-Output-Gigawords}, - AcctOutputPackets = %{request.Acct-Output-Packets}, + AcctSessionTime = %{query.SessionTime}, + AcctInputOctets = %{query.InputOctets}, + AcctInputGigawords = %{query.InputGigawords}, + AcctInputPackets = %{query.InputPackets}, + AcctOutputOctets = %{query.OutputOctets}, + AcctOutputGigawords = %{query.OutputGigawords}, + AcctOutputPackets = %{query.OutputPackets} + WHERE + Username = %{request.User-Name} + AND AcctSessionID = %{request.Acct-Session-Id} + AND NASIPAddress = %{request.NAS-IP-Address} + AND PeriodKey = %{query.PeriodKey} + '; + + $config->{'accounting_stop_status_query'} = ' + UPDATE + @TP@accounting + SET AcctStatusType = %{request.Acct-Status-Type}, AcctTerminateCause = %{request.Acct-Terminate-Cause} WHERE @@ -165,7 +202,7 @@ sub init @TP@accounting WHERE Username = %{request.User-Name} - AND EventTimestamp >= %{query.From} + AND PeriodKey = %{query.PeriodKey} '; # Setup SQL queries @@ -180,6 +217,15 @@ sub init $config->{'accounting_start_query'} = $scfg->{'mod_accounting_sql'}->{'accounting_start_query'}; } } + if (defined($scfg->{'mod_accounting_sql'}->{'accounting_update_get_records_query'}) && + $scfg->{'mod_accounting_sql'}->{'accounting_update_get_records_query'} ne "") { + if (ref($scfg->{'mod_accounting_sql'}->{'accounting_update_get_records_query'}) eq "ARRAY") { + $config->{'accounting_update_get_records_query'} = join(' ', + @{$scfg->{'mod_accounting_sql'}->{'accounting_update_get_records_query'}}); + } else { + $config->{'accounting_update_get_records_query'} = $scfg->{'mod_accounting_sql'}->{'accounting_update_get_records_query'}; + } + } if (defined($scfg->{'mod_accounting_sql'}->{'accounting_update_query'}) && $scfg->{'mod_accounting_sql'}->{'accounting_update_query'} ne "") { if (ref($scfg->{'mod_accounting_sql'}->{'accounting_update_query'}) eq "ARRAY") { @@ -198,6 +244,15 @@ sub init $config->{'accounting_stop_query'} = $scfg->{'mod_accounting_sql'}->{'accounting_stop_query'}; } } + if (defined($scfg->{'mod_accounting_sql'}->{'accounting_stop_status_query'}) && + $scfg->{'mod_accounting_sql'}->{'accounting_stop_status_query'} ne "") { + if (ref($scfg->{'mod_accounting_sql'}->{'accounting_stop_status_query'}) eq "ARRAY") { + $config->{'accounting_stop_status_query'} = join(' ', + @{$scfg->{'mod_accounting_sql'}->{'accounting_stop_status_query'}}); + } else { + $config->{'accounting_stop_status_query'} = $scfg->{'mod_accounting_sql'}->{'accounting_stop_status_query'}; + } + } if (defined($scfg->{'mod_accounting_sql'}->{'accounting_usage_query'}) && $scfg->{'mod_accounting_sql'}->{'accounting_usage_query'} ne "") { if (ref($scfg->{'mod_accounting_sql'}->{'accounting_usage_query'}) eq "ARRAY") { @@ -214,7 +269,7 @@ sub init # Function to get radius user data usage sub getUsage { - my ($server,$user,$packet,$month) = @_; + my ($server,$user,$packet) = @_; # Build template my $template; @@ -222,13 +277,13 @@ sub getUsage $template->{'request'}->{$attr} = $packet->rawattr($attr) } $template->{'user'} = $user; - # Query parameters - $template->{'query'}->{'From'} = $month; + + # Current PeriodKey + my $now = DateTime->now; + $template->{'query'}->{'PeriodKey'} = $now->strftime("%Y-%m"); # Replace template entries - my ($queryString, @params) = templateReplace($config->{'accounting_usage_query'},$template); - # Add month to our params - my @dbDoParams = ($queryString, @params); + my (@dbDoParams) = templateReplace($config->{'accounting_usage_query'},$template); # Fetch data my $sth = DBSelect(@dbDoParams); @@ -296,20 +351,30 @@ sub acct_log { my ($server,$user,$packet) = @_; - # Build template my $template; foreach my $attr ($packet->attributes) { - $template->{'request'}->{$attr} = $packet->rawattr($attr) + $template->{'request'}->{$attr} = $packet->rawattr($attr); } # Fix event timestamp $template->{'request'}->{'Timestamp'} = $user->{'_Internal'}->{'Timestamp'}; + # Add user $template->{'user'} = $user; + # Current PeriodKey + my $now = DateTime->now; + my $periodKey = $now->strftime("%Y-%m"); + + # For our queries + $template->{'query'}->{'PeriodKey'} = $periodKey; + # + # S T A R T P A C K E T + # if ($packet->attr('Acct-Status-Type') eq "Start") { + # Replace template entries my @dbDoParams = templateReplace($config->{'accounting_start_query'},$template); @@ -321,24 +386,197 @@ sub acct_log return MOD_RES_NACK; } + # + # U P D A T E P A C K E T + # + } elsif ($packet->attr('Acct-Status-Type') eq "Alive") { # Replace template entries - my @dbDoParams = templateReplace($config->{'accounting_update_query'},$template); + my @dbDoParams = templateReplace($config->{'accounting_update_get_records_query'},$template); - # Update database - my $sth = DBDo(@dbDoParams); + # Fetch previous records of the same session + my $sth = DBSelect(@dbDoParams); if (!$sth) { - $server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Failed to update accounting ALIVE record: ". - awitpt::db::dblayer::Error()); - return MOD_RES_NACK; + $server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Database query failed: ".awitpt::db::dblayer::Error()); + return; + } + + # Convert session total gigawords/octets into bytes + my $totalInputBytes = Math::BigInt->new(); + $totalInputBytes->badd($template->{'request'}->{'Acct-Input-Gigawords'})->bmul(UINT_MAX); + $totalInputBytes->badd($template->{'request'}->{'Acct-Input-Octets'}); + my $totalOutputBytes = Math::BigInt->new(); + $totalOutputBytes->badd($template->{'request'}->{'Acct-Output-Gigawords'})->bmul(UINT_MAX); + $totalOutputBytes->badd($template->{'request'}->{'Acct-Output-Octets'}); + + # Loop through previous records and subtract them from our session totals + my $startNewPeriod = 0; + $template->{'query'}->{'InputPackets'} = $template->{'request'}->{'Acct-Input-Packets'}; + $template->{'query'}->{'OutputPackets'} = $template->{'request'}->{'Acct-Output-Packets'}; + $template->{'query'}->{'SessionTime'} = $template->{'request'}->{'Acct-Session-Time'}; + while (my $sessionPart = $sth->fetchrow_hashref()) { + $sessionPart = hashifyLCtoMC( + $sessionPart, + qw(InputOctets InputPackets OutputOctets OutputPackets InputGigawords OutputGigawords SessionTime PeriodKey) + ); + + # Convert this session usage to bytes + my $sessionInputBytes = Math::BigInt->new(); + $sessionInputBytes->badd($sessionPart->{'InputGigawods'})->bmul(UINT_MAX); + $sessionInputBytes->badd($sessionPart->{'InputOctets'}); + my $sessionOutputBytes = Math::BigInt->new(); + $sessionOutputBytes->badd($sessionPart->{'OutputGigawods'})->bmul(UINT_MAX); + $sessionOutputBytes->badd($sessionPart->{'OutputOctets'}); + + # Check if this record is from an earlier period + $startNewPeriod = 0; + if (defined($sessionPart->{'PeriodKey'}) && $sessionPart->{'PeriodKey'} ne $periodKey) { + + # Subtract from our total + $totalInputBytes->bsub($sessionInputBytes); + $totalOutputBytes->bsub($sessionOutputBytes); + + # Subtract other usage + if (defined($sessionPart->{'InputPackets'}) && $sessionPart->{'InputPackets'} > 0) { + $template->{'query'}->{'InputPackets'} -= $sessionPart->{'InputPackets'}; + } + if (defined($sessionPart->{'OutputPackets'}) && $sessionPart->{'OutputPackets'} > 0) { + $template->{'query'}->{'OutputPackets'} -= $sessionPart->{'OutputPackets'}; + } + if (defined($sessionPart->{'SessionTime'}) && $sessionPart->{'SessionTime'} > 0) { + $template->{'query'}->{'SessionTime'} -= $sessionPart->{'SessionTime'}; + } + + # We need to continue this session in a new entry + $startNewPeriod = 1; + } } + # Re-calculate + my ($inputGigawordsStr,$inputOctetsStr) = $totalInputBytes->bdiv(UINT_MAX); + my ($outputGigawordsStr,$outputOctetsStr) = $totalOutputBytes->bdiv(UINT_MAX); + + # Conversion to strings + $template->{'query'}->{'InputGigawords'} = $inputGigawordsStr->bstr(); + $template->{'query'}->{'InputOctets'} = $inputOctetsStr->bstr(); + $template->{'query'}->{'OutputGigawords'} = $outputGigawordsStr->bstr(); + $template->{'query'}->{'OutputOctets'} = $outputOctetsStr->bstr(); + + # Check if we doing an update + if ($startNewPeriod == 0) { + # Replace template entries + @dbDoParams = templateReplace($config->{'accounting_update_query'},$template); + + # Update database + my $sth = DBDo(@dbDoParams); + if (!$sth) { + $server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Failed to update accounting ALIVE record: ". + awitpt::db::dblayer::Error()); + return MOD_RES_NACK; + } + # Else do a start record to continue session + } else { + # Replace template entries + my @dbDoParams = templateReplace($config->{'accounting_start_query'},$template); + + # Insert into database + my $sth = DBDo(@dbDoParams); + if (!$sth) { + $server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Failed to insert accounting START record: ". + awitpt::db::dblayer::Error()); + return MOD_RES_NACK; + } + $startNewPeriod = 0; + } + + # + # S T O P P A C K E T + # + } elsif ($packet->attr('Acct-Status-Type') eq "Stop") { + # Replace template entries - my @dbDoParams = templateReplace($config->{'accounting_stop_query'},$template); + my @dbDoParams = templateReplace($config->{'accounting_update_get_records_query'},$template); - # Update database - my $sth = DBDo(@dbDoParams); + # Fetch data + my $sth = DBSelect(@dbDoParams); + if (!$sth) { + $server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Database query failed: ".awitpt::db::dblayer::Error()); + return MOD_RES_NACK; + } + + # Convert session total gigawords/octets into bytes + my $totalInputBytes = Math::BigInt->new(); + $totalInputBytes->badd($template->{'request'}->{'Acct-Input-Gigawords'})->bmul(UINT_MAX); + $totalInputBytes->badd($template->{'request'}->{'Acct-Input-Octets'}); + my $totalOutputBytes = Math::BigInt->new(); + $totalOutputBytes->badd($template->{'request'}->{'Acct-Output-Gigawords'})->bmul(UINT_MAX); + $totalOutputBytes->badd($template->{'request'}->{'Acct-Output-Octets'}); + + # Loop through records and subtract from our totals if needed + $template->{'query'}->{'InputPackets'} = $template->{'request'}->{'Acct-Input-Packets'}; + $template->{'query'}->{'OutputPackets'} = $template->{'request'}->{'Acct-Output-Packets'}; + $template->{'query'}->{'SessionTime'} = $template->{'request'}->{'Acct-Session-Time'}; + while (my $sessionPart = $sth->fetchrow_hashref()) { + $sessionPart = hashifyLCtoMC( + $sessionPart, + qw(InputOctets InputPackets OutputOctets OutputPackets InputGigawords OutputGigawords SessionTime PeriodKey) + ); + + # Convert this session usage to bytes + my $sessionInputBytes = Math::BigInt->new(); + $sessionInputBytes->badd($sessionPart->{'InputGigawods'})->bmul(UINT_MAX); + $sessionInputBytes->badd($sessionPart->{'InputOctets'}); + my $sessionOutputBytes = Math::BigInt->new(); + $sessionOutputBytes->badd($sessionPart->{'OutputGigawods'})->bmul(UINT_MAX); + $sessionOutputBytes->badd($sessionPart->{'OutputOctets'}); + + # Subtract this period/session usage from total + if (defined($sessionPart->{'PeriodKey'}) && $sessionPart->{'PeriodKey'} ne $periodKey) { + + # Subtract from our total + $totalInputBytes->bsub($sessionInputBytes); + $totalOutputBytes->bsub($sessionInputBytes); + + # Subtract other usage + if (defined($sessionPart->{'InputPackets'}) && $sessionPart->{'InputPackets'} > 0) { + $template->{'query'}->{'InputPackets'} -= $sessionPart->{'InputPackets'}; + } + if (defined($sessionPart->{'OutputPackets'}) && $sessionPart->{'OutputPackets'} > 0) { + $template->{'query'}->{'OutputPackets'} -= $sessionPart->{'OutputPackets'}; + } + if (defined($sessionPart->{'SessionTime'}) && $sessionPart->{'SessionTime'} > 0) { + $template->{'query'}->{'SessionTime'} -= $sessionPart->{'SessionTime'}; + } + } + } + DBFreeRes($sth); + + # Re-calculate + my ($inputGigawordsStr,$inputOctetsStr) = $totalInputBytes->bdiv(UINT_MAX); + my ($outputGigawordsStr,$outputOctetsStr) = $totalOutputBytes->bdiv(UINT_MAX); + + # Conversion to strings + $template->{'query'}->{'InputGigawords'} = $inputGigawordsStr->bstr(); + $template->{'query'}->{'InputOctets'} = $inputOctetsStr->bstr(); + $template->{'query'}->{'OutputGigawords'} = $outputGigawordsStr->bstr(); + $template->{'query'}->{'OutputOctets'} = $outputOctetsStr->bstr(); + + # Replace template entries + @dbDoParams = templateReplace($config->{'accounting_stop_query'},$template); + + # Update database (totals) + $sth = DBDo(@dbDoParams); + if (!$sth) { + $server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Failed to update accounting STOP record: ".awitpt::db::dblayer::Error()); + return MOD_RES_NACK; + } + + # Replace template entries + @dbDoParams = templateReplace($config->{'accounting_stop_status_query'},$template); + + # Update database (status) + $sth = DBDo(@dbDoParams); if (!$sth) { $server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Failed to update accounting STOP record: ".awitpt::db::dblayer::Error()); return MOD_RES_NACK; @@ -353,12 +591,12 @@ sub acct_log sub cleanup { my ($server) = @_; - my ($prevYear,$prevMonth); # The datetime now.. my $now = DateTime->now; # If this is a new year + my ($prevYear,$prevMonth); if ($now->month == 1) { $prevYear = $now->year - 1; $prevMonth = 12; @@ -369,8 +607,9 @@ sub cleanup # New datetime my $lastMonth = DateTime->new( year => $prevYear, month => $prevMonth, day => 1 ); + my $periodKey = $lastMonth->strftime("%Y-%m"); - # Update totals for last month + # Select totals for last month my $sth = DBSelect(' SELECT Username, @@ -382,11 +621,11 @@ sub cleanup FROM @TP@accounting WHERE - EventTimestamp > ? + PeriodKey = ? GROUP BY Username ', - $lastMonth->ymd + $periodKey ); if (!$sth) { @@ -396,67 +635,87 @@ sub cleanup } # Set blank array - my @allRecords = (); + my @allRecords; - my $i = 0; # Load items into array + my $index = 0; while (my $usageTotals = $sth->fetchrow_hashref()) { $usageTotals = hashifyLCtoMC( $usageTotals, qw(Username AcctSessionTime AcctInputOctets AcctInputGigawords AcctOutputOctets AcctOutputGigawords) ); - # Set array blank - my @recordRow = (); - # Set array items - @recordRow = ( - $usageTotals->{'Username'}, - $lastMonth->ymd, - $usageTotals->{'AcctSessionTime'}, - $usageTotals->{'AcctInputOctets'}, - $usageTotals->{'AcctInputGigawords'}, - $usageTotals->{'AcctOutputOctets'}, - $usageTotals->{'AcctOutputGigawords'} - ); - - # Add record ontp @allRecords - @{$allRecords[$i]} = @recordRow; - - # Increate array size - $i++; + $allRecords[$index] = { + Username => $usageTotals->{'Username'}, + PeriodKey => $lastMonth->ymd, + SessionTime => $usageTotals->{'AcctSessionTime'}, + InputOctets => $usageTotals->{'AcctInputOctets'}, + InputGigawords => $usageTotals->{'AcctInputGigawords'}, + OutputOctets => $usageTotals->{'AcctOutputOctets'}, + OutputGigawords => $usageTotals->{'AcctOutputGigawords'} + }; + + # Increase size + $index++; } # Begin transaction DBBegin(); - my @dbDoParams = (); - my $count = length(@allRecords); - # Update totals for last month - for ($i = 0; $i < $count; $i++) { + if ($index > 0) { + + # Delete duplicate records + my @dbDoParams; @dbDoParams = (' - INSERT INTO + DELETE FROM @TP@accounting_summary - ( - Username, - PeriodKey, - AcctSessionTime, - AcctInputOctets, - AcctInputGigawords, - AcctOutputOctets, - AcctOutputGigawords - ) - VALUES - (?,?,?,?,?,?,?) - ', - @{$allRecords[$i]} + WHERE + PeriodKey = ?', + $lastMonth->ymd ); if ($sth) { # Do query $sth = DBDo(@dbDoParams); } + + my @insertArray; + for (my $i = 0; $i < $index; $i++) { + @insertArray = ( + $allRecords[$i]->{'Username'}, + $allRecords[$i]->{'PeriodKey'}, + $allRecords[$i]->{'SessionTime'}, + $allRecords[$i]->{'InputOctets'}, + $allRecords[$i]->{'InputGigawords'}, + $allRecords[$i]->{'OutputOctets'}, + $allRecords[$i]->{'OutputGigawords'} + ); + + @dbDoParams = (' + INSERT INTO + @TP@accounting_summary + ( + Username, + PeriodKey, + AcctSessionTime, + AcctInputOctets, + AcctInputGigawords, + AcctOutputOctets, + AcctOutputGigawords + ) + VALUES + (?,?,?,?,?,?,?) + ', + @insertArray + ); + + if ($sth) { + # Do query + $sth = DBDo(@dbDoParams); + } + } } # Rollback with error if failed diff --git a/smradius/modules/features/mod_feature_capping.pm b/smradius/modules/features/mod_feature_capping.pm index 90fe2954..258ebd9d 100644 --- a/smradius/modules/features/mod_feature_capping.pm +++ b/smradius/modules/features/mod_feature_capping.pm @@ -118,14 +118,13 @@ sub post_auth_hook # Get the users' usage my $accountingUsage; - my $month = DateTime->from_epoch( epoch => $user->{'_Internal'}->{'Timestamp-Unix'} )->strftime('%Y-%m'); # Loop with plugins to find anyting supporting getting of usage foreach my $module (@{$server->{'module_list'}}) { # Do we have the correct plugin? if ($module->{'Accounting_getUsage'}) { $server->log(LOG_INFO,"[MOD_FEATURE_CAPPING] Found plugin: '".$module->{'Name'}."'"); # Fetch users session uptime & bandwidth used - my $res = $module->{'Accounting_getUsage'}($server,$user,$packet,$month); + my $res = $module->{'Accounting_getUsage'}($server,$user,$packet); if (!defined($res)) { $server->log(LOG_ERR,"[MOD_FEATURE_CAPPING] No usage data found for user '".$packet->attr('User-Name')."'"); return MOD_RES_SKIP; @@ -318,7 +317,7 @@ sub post_acct_hook if ($module->{'Accounting_getUsage'}) { $server->log(LOG_INFO,"[MOD_FEATURE_CAPPING] Found plugin: '".$module->{'Name'}."'"); # Fetch users session uptime & bandwidth used - my $res = $module->{'Accounting_getUsage'}($server,$user,$packet,$month); + my $res = $module->{'Accounting_getUsage'}($server,$user,$packet); if (!defined($res)) { $server->log(LOG_ERR,"[MOD_FEATURE_CAPPING] No usage data found for user '".$packet->attr('User-Name')."'"); return MOD_RES_SKIP; diff --git a/smradius/modules/system/mod_config_sql_topups.pm b/smradius/modules/system/mod_config_sql_topups.pm index 3d8b3d4f..31f665f1 100644 --- a/smradius/modules/system/mod_config_sql_topups.pm +++ b/smradius/modules/system/mod_config_sql_topups.pm @@ -301,6 +301,7 @@ sub cleanup $prevMonth = $now->month - 1; } my $lastMonth = DateTime->new( year => $prevYear, month => $prevMonth, day => 1 ); + my $prevPeriodKey = $lastMonth->strftime("%Y-%m"); # Get begin date of next month my ($folYear,$folMonth); @@ -335,12 +336,12 @@ sub cleanup FROM @TP@accounting WHERE - EventTimestamp > ? + PeriodKey = ? AND Username = ? GROUP BY Username ', - $lastMonth,$userName + $prevPeriodKey,$userName ); if (!$sth) { @@ -428,7 +429,6 @@ sub cleanup # Get users topups that are still valid from topups_summary, must not be depleted - my $prevPeriodKey = $lastMonth->strftime("%Y-%m"); $sth = DBSelect(' SELECT @TP@topups_summary.TopupID, @@ -783,7 +783,24 @@ sub cleanup # Loop through summary topups foreach my $summaryTopup (@summaryTopups) { - # Set users depleted topups + + # Delete any previous record of this summary + $sth = DBDo(' + DELETE FROM + @TP@topups_summary + WHERE + TopupID = ? + AND PeriodKey = ?', + $summaryTopup->{'ID'}, $periodKey + ); + + if (!$sth) { + $server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to delete previous record: ". + awitpt::db::dblayer::Error()); + goto FAIL_ROLLBACK; + } + + # Insert topup summary $sth = DBDo(' INSERT INTO @TP@topups_summary (TopupID,PeriodKey,Balance) diff --git a/smradiusd.conf b/smradiusd.conf index 1719bad6..7d951de2 100644 --- a/smradiusd.conf +++ b/smradiusd.conf @@ -235,7 +235,8 @@ accounting_start_query=<<EOT AcctStatusType, NASIdentifier, NASIPAddress, - AcctDelayTime + AcctDelayTime, + PeriodKey ) VALUES ( @@ -254,39 +255,74 @@ accounting_start_query=<<EOT %{request.Acct-Status-Type}, %{request.NAS-Identifier}, %{request.NAS-IP-Address}, - %{request.Acct-Delay-Time} + %{request.Acct-Delay-Time}, + %{query.PeriodKey} ) EOT +accounting_update_get_records_query=<<EOT + SELECT + SUM(AcctInputOctets) AS InputOctets, + SUM(AcctInputPackets) AS InputPackets, + SUM(AcctOutputOctets) AS OutputOctets, + SUM(AcctOutputPackets) AS OutputPackets, + SUM(AcctInputGigawords) AS InputGigawords, + SUM(AcctOutputGigawords) AS OutputGigawords, + SUM(AcctSessionTime) AS SessionTime, + PeriodKey + FROM + @TP@accounting + WHERE + Username = %{request.User-Name} + AND AcctSessionID = %{request.Acct-Session-Id} + AND NASIPAddress = %{request.NAS-IP-Address} + GROUP BY + PeriodKey + ORDER BY + ID ASC +EOT + accounting_update_query=<<EOT UPDATE @TP@accounting SET - AcctSessionTime = %{request.Acct-Session-Time}, - AcctInputOctets = %{request.Acct-Input-Octets}, - AcctInputGigawords = %{request.Acct-Input-Gigawords}, - AcctInputPackets = %{request.Acct-Input-Packets}, - AcctOutputOctets = %{request.Acct-Output-Octets}, - AcctOutputGigawords = %{request.Acct-Output-Gigawords}, - AcctOutputPackets = %{request.Acct-Output-Packets}, + AcctSessionTime = %{query.SessionTime}, + AcctInputOctets = %{query.InputOctets}, + AcctInputGigawords = %{query.InputGigawords}, + AcctInputPackets = %{query.InputPackets}, + AcctOutputOctets = %{query.OutputOctets}, + AcctOutputGigawords = %{query.OutputGigawords}, + AcctOutputPackets = %{query.OutputPackets}, AcctStatusType = %{request.Acct-Status-Type} WHERE Username = %{request.User-Name} AND AcctSessionID = %{request.Acct-Session-Id} AND NASIPAddress = %{request.NAS-IP-Address} + AND PeriodKey = %{query.PeriodKey} EOT accounting_stop_query=<<EOT UPDATE @TP@accounting SET - AcctSessionTime = %{request.Acct-Session-Time}, - AcctInputOctets = %{request.Acct-Input-Octets}, - AcctInputGigawords = %{request.Acct-Input-Gigawords}, - AcctInputPackets = %{request.Acct-Input-Packets}, - AcctOutputOctets = %{request.Acct-Output-Octets}, - AcctOutputGigawords = %{request.Acct-Output-Gigawords}, - AcctOutputPackets = %{request.Acct-Output-Packets}, + AcctSessionTime = %{query.SessionTime}, + AcctInputOctets = %{query.InputOctets}, + AcctInputGigawords = %{query.InputGigawords}, + AcctInputPackets = %{query.InputPackets}, + AcctOutputOctets = %{query.OutputOctets}, + AcctOutputGigawords = %{query.OutputGigawords}, + AcctOutputPackets = %{query.OutputPackets} + WHERE + Username = %{request.User-Name} + AND AcctSessionID = %{request.Acct-Session-Id} + AND NASIPAddress = %{request.NAS-IP-Address} + AND PeriodKey = %{query.PeriodKey} +EOT + +accounting_stop_status_query=<<EOT + UPDATE + @TP@accounting + SET AcctStatusType = %{request.Acct-Status-Type}, AcctTerminateCause = %{request.Acct-Terminate-Cause} WHERE @@ -306,7 +342,7 @@ accounting_usage_query=<<EOT @TP@accounting WHERE Username = %{request.User-Name} - AND EventTimestamp >= %{query.From} + AND PeriodKey = %{query.PeriodKey} EOT -- GitLab