From 2f84a81efd136750137c395f09fce10d2ab9ca44 Mon Sep 17 00:00:00 2001
From: Robert Anderson <randerson@lbsd.net>
Date: Tue, 9 Jun 2009 10:39:55 +0000
Subject: [PATCH] Topups summary support for smadmin tool

---
 .../modules/config/mod_config_sql_topups.pm   | 363 +++++++++++++++++-
 1 file changed, 352 insertions(+), 11 deletions(-)

diff --git a/smradius/modules/config/mod_config_sql_topups.pm b/smradius/modules/config/mod_config_sql_topups.pm
index 9fc1f74b..fa487a20 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
-- 
GitLab