diff --git a/smradius/modules/config/mod_config_sql_topups.pm b/smradius/modules/config/mod_config_sql_topups.pm index 9fc1f74b533a2db667aeef375d0b1dc561564951..fa487a203ef3dae556808d978f38a232f068a053 100644 --- a/smradius/modules/config/mod_config_sql_topups.pm +++ b/smradius/modules/config/mod_config_sql_topups.pm @@ -1,4 +1,4 @@ -# SQL config database support +# Topup support # Copyright (C) 2007-2009, AllWorldIT # # This program is free software; you can redistribute it and/or modify @@ -26,6 +26,12 @@ use smradius::logging; use smradius::dblayer; use smradius::util; use smradius::attributes; + +use POSIX qw(ceil); +use DateTime; +use Date::Parse; + + # Exporter stuff require Exporter; @@ -40,9 +46,12 @@ our (@ISA,@EXPORT,@EXPORT_OK); # Plugin info our $pluginInfo = { - Name => "SQL Topups Database", + Name => "SQL Topup Config", Init => \&init, - + + # Cleanup run by smadmin + Cleanup => \&cleanup, + # User database Config_get => \&getTopups }; @@ -67,17 +76,17 @@ sub init # Default configs... $config->{'get_topups_query'} = ' SELECT - @TP@topups.Type, - @TP@topups.ValidFrom, - @TP@topups.ValidTo, - @TP@topups.Value + @TP@topups.Type, + @TP@topups.ValidFrom, + @TP@topups.ValidTo, + @TP@topups.Value FROM - @TP@topups, - @TP@users + @TP@topups, + @TP@users WHERE - @TP@topups.UserID = @TP@users.ID + @TP@topups.UserID = @TP@users.ID AND - @TP@users.Username = ? + @TP@users.Username = ? '; @@ -140,5 +149,337 @@ sub getTopups } +# Topup summary function +sub cleanup +{ + my ($server) = @_; + my $sth; + + + # TODO - be more dynamic, we may not be using SQL users + # Get all usernames + $sth = DBSelect('SELECT ID, Username FROM @TP@users'); + + if (!$sth) { + $server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to select from users: ". + smradius::dblayer::Error()); + return; + } + + # Create hash of usernames + my %users; + while (my $user = $sth->fetchrow_hashref()) { + $users{$user->{'id'}} = $user->{'username'}; + } + + # Finished for now + DBFreeRes($sth); + + # The datetime now + my $now = DateTime->now; + # Make datetime for a month ago + my $thisMonth = DateTime->new( year => $now->year, month => $now->month, day => 1 ); + + # Get begin date of last month + my ($prevYear,$prevMonth); + if ($now->month == 1) { + $prevYear = $now->year - 1; + $prevMonth = 12; + } else { + $prevYear = $now->year; + $prevMonth = $now->month - 1; + } + my $lastMonth = DateTime->new( year => $prevYear, month => $prevMonth, day => 1 ); + + # Get begin date of next month + my ($folYear,$folMonth); + if ($now->month == 12) { + $folYear = $now->year + 1; + $folMonth = 1; + } else { + $folYear = $now->year; + $folMonth = $now->month + 1; + } + my $nextMonth = DateTime->new( year => $folYear, month => $folMonth, day => 1 ); + my $unix_nextMonth = $nextMonth->epoch(); + + # Start of multiple queries + DBBegin(); + + # Loop through users and check each topup, setting it to default if needed and updating summary + foreach my $userID (keys %users) { + my $userName = $users{$userID}; + + # TODO - in future we must be more dynamic, we may not be using SQL accunting + # Get current usage + $sth = DBSelect(' + SELECT + Username, + SUM(AcctSessionTime) as AcctSessionTime, + SUM(AcctInputOctets) as AcctInputOctets, + SUM(AcctInputGigawords) as AcctInputGigawords, + SUM(AcctOutputOctets) as AcctOutputOctets, + SUM(AcctOutputGigawords) as AcctOutputGigawords + FROM + @TP@accounting + WHERE + EventTimestamp > ? + AND Username = ? + GROUP BY + Username + ', + $lastMonth,$userName + ); + + if (!$sth) { + $server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to select accounting records: ". + smradius::dblayer::Error()); + goto FAIL_ROLLBACK; + } + + # Pull data + my $row = $sth->fetchrow_hashref(); + + # Total up input + my $totalData = 0; + if (defined($row->{'acctinputoctets'}) && $row->{'acctinputoctets'} > 0) { + $totalData += $row->{'acctinputoctets'} / 1024 / 1024; + } + if (defined($row->{'acctinputgigawords'}) && $row->{'acctinputgigawords'} > 0) { + $totalData += $row->{'acctinputgigawords'} * 4096; + } + # Add up output + if (defined($row->{'acctoutputoctets'}) && $row->{'acctoutputoctets'} > 0) { + $totalData += $row->{'acctoutputoctets'} / 1024 / 1024; + } + if (defined($row->{'acctoutputgigawords'}) && $row->{'acctoutputgigawords'} > 0) { + $totalData += $row->{'acctoutputgigawords'} * 4096; + } + + # Add up uptime + my $totalTime = 0; + if (defined($row->{'acctsessiontime'}) && $row->{'acctsessiontime'} > 0) { + $totalTime = $row->{'acctsessiontime'} / 60; + } + + # Rounding up + my %res; + $res{'TotalTrafficUsage'} = ceil($totalData); + $res{'TotalUptimeUsage'} = ceil($totalTime); + + # Finished for now + DBFreeRes($sth); + + # TODO? + # FIXME - support for groups and realm config? + + # Get users caps + $sth = DBSelect(' + SELECT + @TP@user_attributes.Name, @TP@user_attributes.Value + FROM + @TP@user_attributes, @TP@users + WHERE + @TP@users.Username = ? + ', + $userName + ); + + if (!$sth) { + $server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to select usage caps: ". + smradius::dblayer::Error()); + goto FAIL_ROLLBACK; + } + + # Add cap limits to capRecord hash + my %capRecord; + while ($row = $sth->fetchrow_hashref()) { + if ($row->{'name'} eq 'SMRadius-Capping-Traffic-Limit') { + $capRecord{'TrafficLimit'} = $row->{'value'}; + } + if ($row->{'name'} eq 'SMRadius-Capping-Uptime-Limit') { + $capRecord{'UptimeLimit'} = $row->{'value'}; + } + } + + # Finished for now + DBFreeRes($sth); + + # Set excess traffic / uptime used + my $excessUptime = 0; + my $excessTraffic = 0; + if (defined($capRecord{'TrafficLimit'})) { + $excessTraffic = $res{'TotalTrafficUsage'} - $capRecord{'TrafficLimit'}; + } + if (defined($capRecord{'UptimeLimit'})) { + $excessUptime = $res{'TotalUptimeUsage'} - $capRecord{'UptimeLimit'}; + } + + # Get users topups not depleted + $sth = DBSelect(' + SELECT + @TP@topups.ID, @TP@topups.Value, @TP@topups.Type, @TP@topups.ValidTo + FROM + @TP@topups, @TP@users + WHERE + @TP@topups.Depleted = 0 + AND @TP@topups.UserID = @TP@users.ID + AND @TP@users.Username = ? + AND @TP@topups.ValidFrom <= ? + AND @TP@topups.ValidTo >= ? + ORDER BY + @TP@topups.Timestamp + ', + $userName,$lastMonth,$thisMonth + ); + + if (!$sth) { + $server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to select topups: ". + smradius::dblayer::Error()); + goto FAIL_ROLLBACK; + } + + # Loop with the topups and push them into arrays + my @trafficTopups = (); + my @uptimeTopups = (); + while (my $row = $sth->fetchrow_hashref()) { + # Convert string to unix time + my $unix_validto = str2time($row->{'validto'}); + # If this is a traffic topup ... + if ($row->{'type'} == 1) { + push(@trafficTopups, { + ID => $row->{'id'}, + Value => $row->{'value'}, + ValidTo => $unix_validto + }); + + # Or a uptime topup... + } elsif ($row->{'type'} == 2) { + push(@uptimeTopups, { + ID => $row->{'id'}, + Value => $row->{'value'}, + ValidTo => $unix_validto + }); + } + } + + #my %topups = ( Traffic => \@trafficTopups, Uptime => \@uptimeTopups ); + + # Finished for now + DBFreeRes($sth); + + # List of topups to be set depleted + my @depletedTopups = (); + + # Working with traffic + if (defined($excessTraffic) && $excessTraffic gt 0) { + foreach my $topup (@trafficTopups) { + # Topup depleted + if ($topup->{'Value'} < $excessTraffic) { + # Add topup ID to depleted list + push(@depletedTopups, $topup->{'ID'}); + # Excess traffic remaining + $excessTraffic -= $topup->{'Value'}; + + # Topup still alive + } else { + my $trafficRemaining = $topup->{'Value'} - $excessTraffic; + + if ($topup->{'ValidTo'} >= $unix_nextMonth) { + + # Set users depleted topups + $sth = DBDo(' + INSERT INTO + @TP@topups_summary (TopupID,PeriodKey,Balance) + VALUES + (?,?,?) + ', + $topup->{'ID'},$thisMonth,$trafficRemaining + ); + if (!$sth) { + $server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to update topup summary: ". + smradius::dblayer::Error()); + goto FAIL_ROLLBACK; + } + } + + # We dont want to continue if a non depleted topup found + last; + } + } + } + + # Working with uptime + if (defined($excessUptime) && $excessUptime gt 0) { + foreach my $topup (@uptimeTopups) { + # Topup depleted + if ($topup->{'Value'} < $excessUptime) { + # Add topup ID to depleted list + push(@depletedTopups, $topup->{'ID'}); + # Excess uptime remaining + $excessUptime -= $topup->{'Value'}; + + # Topup still alive + } else { + my $uptimeRemaining = $topup->{'Value'} - $excessUptime; + + if ($topup->{'ValidTo'} >= $unix_nextMonth) { + + # Set users depleted topups + $sth = DBDo(' + INSERT INTO + @TP@topups_summary (TopupID,PeriodKey,Balance) + VALUES + (?,?,?) + ', + $topup->{'ID'},$thisMonth,$uptimeRemaining + ); + if (!$sth) { + $server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to update topups: ". + smradius::dblayer::Error()); + goto FAIL_ROLLBACK; + } + } + + # We dont want to continue if a non depleted topup found + last; + } + } + } + + # Loop through topups that are depleted + my $topupID; + foreach $topupID (@depletedTopups) { + # Set users depleted topups + $sth = DBSelect(' + UPDATE + @TP@topups + SET + Depleted = 1 + WHERE + ID = ? + ', + $topupID + ); + if (!$sth) { + $server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to update topups: ". + smradius::dblayer::Error()); + goto FAIL_ROLLBACK; + } + } + } + + # Finished + $server->log(LOG_NOTICE,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Topup summaries have been updated"); + DBCommit(); + return; + +FAIL_ROLLBACK: + DBRollback(); + $server->log(LOG_NOTICE,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Database has been rolled back, no records updated"); + return; +} + + 1; # vim: ts=4