From 7165e3dab5982dfd602d17a1e029d73c6e39636b Mon Sep 17 00:00:00 2001
From: Nigel Kukard <nkukard@lbsd.net>
Date: Sun, 5 Jan 2014 17:41:29 +0000
Subject: [PATCH] Reworked configmanager API

---
 opentrafficshaper/plugins/configmanager.pm | 3068 +++++++++++++-------
 1 file changed, 2078 insertions(+), 990 deletions(-)

diff --git a/opentrafficshaper/plugins/configmanager.pm b/opentrafficshaper/plugins/configmanager.pm
index 08cb6ba..aa80af1 100644
--- a/opentrafficshaper/plugins/configmanager.pm
+++ b/opentrafficshaper/plugins/configmanager.pm
@@ -1,5 +1,5 @@
 # OpenTrafficShaper configuration manager
-# Copyright (C) 2007-2013, AllWorldIT
+# Copyright (C) 2007-2014, AllWorldIT
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -40,41 +40,63 @@ our (@ISA,@EXPORT,@EXPORT_OK);
 @EXPORT = qw(
 );
 @EXPORT_OK = qw(
-		getLimit
-		getLimits
-		getLimitUsername
-		getLimitTxInterface
-		getLimitRxInterface
-		getLimitMatchPriority
-		setLimitAttribute
-		getLimitAttribute
-		removeLimitAttribute
-
-		getOverride
-		getOverrides
-
-		getShaperState
-		setShaperState
-
-		getTrafficClasses
-		getTrafficClassName
-		isTrafficClassValid
-
-		getTrafficPriority
-
-		getTrafficDirection
-
-		getInterface
-		getInterfaces
-		getInterfaceClasses
-		getInterfaceDefaultPool
-		getInterfaceRate
-		getInterfaceGroup
-		getInterfaceGroups
-		isInterfaceGroupValid
-
-		getMatchPriorities
-		isMatchPriorityValid
+	createLimit
+	getLimit
+	getLimits
+	getLimitUsername
+
+	getOverride
+	getOverrides
+
+	createPool
+	getPools
+	getPool
+	getPoolByIdentifer
+	getPoolTxInterface
+	getPoolRxInterface
+	getPoolTrafficClassID
+	setPoolAttribute
+	getPoolAttribute
+	removePoolAttribute
+	getPoolShaperState
+	setPoolShaperState
+	isPoolIDValid
+	isPoolReady
+
+	getEffectivePool
+
+	getPoolMembers
+	getPoolMember
+	getPoolMembersByIP
+	getPoolMemberMatchPriority
+	setPoolMemberShaperState
+	getPoolMemberShaperState
+	getPoolMemberMatchPriority
+	setPoolMemberAttribute
+	getPoolMemberAttribute
+	removePoolMemberAttribute
+	isPoolMemberReady
+
+	getTrafficClasses
+	getAllTrafficClasses
+	getTrafficClassName
+	isTrafficClassIDValid
+
+	getTrafficClassPriority
+
+	getTrafficDirection
+
+	getInterface
+	getInterfaces
+	getInterfaceTrafficClasses
+	getInterfaceDefaultPool
+	getInterfaceRate
+	getInterfaceGroup
+	getInterfaceGroups
+	isInterfaceGroupIDValid
+
+	getMatchPriorities
+	isMatchPriorityIDValid
 );
 
 use constant {
@@ -91,58 +113,130 @@ use constant {
 	STATE_SYNC_INTERVAL => 300,
 };
 
-# Mandatory config attributes
-sub LIMIT_REQUIRED_ATTRIBUTES {
+
+# Mandatory pool attributes
+sub POOL_REQUIRED_ATTRIBUTES {
 	qw(
-		Username IP
-		InterfaceGroupID MatchPriorityID
-		GroupID ClassID
-		TrafficLimitTx TrafficLimitRx
-		Status
+		Identifier
+		InterfaceGroupID
+		ClassID TrafficLimitTx TrafficLimitRx
+		Source
+	)
+}
+
+# Pool attributes that can be changed
+sub POOL_CHANGE_ATTRIBUTES {
+	qw(
+		FriendlyName
+		ClassID TrafficLimitTx TrafficLimitRx TrafficLimitTxBurst TrafficLimitRxBurst
+		Expires
+		Notes
+	)
+}
+
+# Pool persistent attributes
+sub POOL_PERSISTENT_ATTRIBUTES {
+	qw(
+		Identifier
+		FriendlyName
+		InterfaceGroupID
+		ClassID TrafficLimitTx TrafficLimitRx TrafficLimitTxBurst TrafficLimitRxBurst
+		Expires
+		Source
+		Notes
+	)
+}
+
+
+# Mandatory pool member attributes
+sub POOLMEMBER_REQUIRED_ATTRIBUTES {
+	qw(
+		Username IPAddress
+		MatchPriorityID
+		PoolID
+		GroupID
 		Source
 	)
 }
 
-# Limit Changeset attributes - things that can be changed on the fly in the shaper
-sub LIMIT_CHANGESET_ATTRIBUTES {
+# Pool member attributes that can be changed
+sub POOLMEMBER_CHANGE_ATTRIBUTES {
+	qw(
+		FriendlyName
+		Expires
+		Notes
+	)
+}
+
+# Pool member persistent attributes
+sub POOLMEMBER_PERSISTENT_ATTRIBUTES {
 	qw(
-		ClassID
-		TrafficLimitTx TrafficLimitRx TrafficLimitTxBurst TrafficLimitRxBurst
+		FriendlyName
+		Username IPAddress
+		MatchPriorityID
+		PoolID
+		GroupID
+		Source
+		Expires
+		Notes
 	)
 }
 
-# Persistent attributes supported
-sub LIMIT_PERSISTENT_ATTRIBUTES {
+
+# Mandatory limit attributes
+sub LIMIT_REQUIRED_ATTRIBUTES {
 	qw(
-		Username IP
+		Username IPAddress
 		InterfaceGroupID MatchPriorityID
-		GroupID ClassID
-		TrafficLimitTx TrafficLimitRx TrafficLimitTxBurst TrafficLimitRxBurst
-		FriendlyName Notes
-		Expires Created
+		GroupID
+		ClassID	TrafficLimitTx TrafficLimitRx
+		Status
 		Source
 	)
 }
 
+
 # Override match attributes, one is required
 sub OVERRIDE_MATCH_ATTRIBUTES {
 	qw(
-		Username IP
+		PoolIdentifier Username IPAddress
 		GroupID
 	)
 }
+
+# Override attributes
+sub OVERRIDE_ATTRIBUTES {
+	qw(
+		FriendlyName
+		PoolIdentifier Username IPAddress GroupID
+		ClassID TrafficLimitTx TrafficLimitRx TrafficLimitTxBurst TrafficLimitRxBurst
+		Expires
+		Notes
+	)
+}
+
+# Override attributes that can be changed
+sub OVERRIDE_CHANGE_ATTRIBUTES {
+	qw(
+		FriendlyName
+		ClassID TrafficLimitTx TrafficLimitRx TrafficLimitTxBurst TrafficLimitRxBurst
+		Expires
+		Notes
+	)
+}
+
 # Override changeset attributes
 sub OVERRIDE_CHANGESET_ATTRIBUTES {
 	qw(
-		ClassID
-		TrafficLimitTx TrafficLimitRx TrafficLimitTxBurst TrafficLimitRxBurst
+		ClassID TrafficLimitTx TrafficLimitRx TrafficLimitTxBurst TrafficLimitRxBurst
 	)
 }
+
 # Override attributes supported for persistent storage
 sub OVERRIDE_PERSISTENT_ATTRIBUTES {
 	qw(
 		FriendlyName
-		Username IP GroupID
+		PoolIdentifier Username IPAddress GroupID
 		ClassID TrafficLimitTx TrafficLimitRx TrafficLimitTxBurst TrafficLimitRxBurst
 		Notes
 		Expires Created
@@ -150,15 +244,7 @@ sub OVERRIDE_PERSISTENT_ATTRIBUTES {
 		LastUpdate
 	)
 }
-# Override match criteria
-sub OVERRIDE_MATCH_CRITERIA {
-	(
-		['GroupID'], ['Username'], ['IP'],
-		['GroupID','Username'], ['GroupID','IP'],
-		['Username','IP'],
-		['GroupID','Username','IP'],
-	)
-}
+
 
 
 
@@ -211,22 +297,25 @@ my $lastCleanup = time();
 my $stateChanged = 0;
 my $lastStateSync = time();
 
+
 #
-# LIMITS
+# INTERFACES
 #
-# Supoprted user attributes:
-# * Username
-#    - Users username
-# * IP
-#    - Users IP
-# * GroupID
-#    - Group ID
-# * InterfaceGroupID
-#    - Interface group this limit is attached to
-# * MatchPriorityID
-#    - Match priority on the backend of this limit
+my $interfaceIPMap = {};
+
+
+#
+# POOLS
+#
+# Parameters:
+# * FriendlyName
+#    - Used for display purposes
+# * Identifier
+#    - Unix timestamp when this entry expires, 0 if never
 # * ClassID
 #    - Class ID
+# * InterfaceGroupID
+#    - Interface group this pool is attached to
 # * TrafficLimitTx
 #    - Traffic limit in kbps
 # * TrafficLimitRx
@@ -235,6 +324,31 @@ my $lastStateSync = time();
 #    - Traffic bursting limit in kbps
 # * TrafficLimitRxBurst
 #    - Traffic bursting limit in kbps
+# * Notes
+#    - Notes on this limit
+# * Source
+#    - This is the source of the limit, typically plugin.ModuleName
+my $pools = { };
+my $poolIdentifierMap = { };
+my $poolIDCounter = 1;
+
+
+#
+# POOL MEMBERS
+#
+# Supoprted user attributes:
+# * PoolID
+#    - Pool ID
+# * Username
+#    - Users username
+# * IPAddress
+#    - Users IP address
+# * GroupID
+#    - Group ID
+# * MatchPriorityID
+#    - Match priority on the backend of this limit
+# * ClassID
+#    - Class ID
 # * Expires
 #    - Unix timestamp when this entry expires, 0 if never
 # * FriendlyName
@@ -247,21 +361,22 @@ my $lastStateSync = time();
 #    - online
 #    - unknown
 # * Source
-#    - This is the source of the limit, typically  plugin.ModuleName
-my $limitChangeQueue = { };
-my $limits = { };
-my $limitIPMap = { };
-my $limitIDMap = { };
-my $limitIDCounter = 1;
+#    - This is the source of the limit, typically plugin.ModuleName
+my $poolMembers = { };
+my $poolMemberIDCounter = 1;
+my $poolMemberMap = { };
+
 
 #
 # OVERRIDES
 #
 # Selection criteria:
+# * PoolIdentifier
+#    - Pool identifier
 # * Username
 #    - Users username
-# * IP
-#    - Users IP
+# * IPAddress
+#    - Users IP address
 # * GroupID
 #    - Group ID
 #
@@ -285,14 +400,15 @@ my $limitIDCounter = 1;
 # * Notes
 #    - Notes on this limit
 # * Source
-#    - This is the source of the limit, typically  plugin.ModuleName
-my $overrideChangeQueue = { };
+#    - This is the source of the limit, typically plugin.ModuleName
 my $overrides = { };
-my $overrideMap = { };
-my $overrideIDMap = { };
 my $overrideIDCounter = 1;
 
 
+# Global change queues
+my $poolChangeQueue = { };
+my $poolMemberChangeQueue = { };
+
 
 
 # Initialize plugin
@@ -304,7 +420,9 @@ sub plugin_init
 	# Setup our environment
 	$logger = $globals->{'logger'};
 
-	$logger->log(LOG_NOTICE,"[CONFIGMANAGER] OpenTrafficShaper Config Manager v".VERSION." - Copyright (c) 2013, AllWorldIT");
+	$logger->log(LOG_NOTICE,"[CONFIGMANAGER] OpenTrafficShaper Config Manager v%s - Copyright (c) 2007-2014, AllWorldIT",
+			VERSION
+	);
 
 	# If we have global config, use it
 	my $gconfig = { };
@@ -333,17 +451,17 @@ sub plugin_init
 		# Split off group ID and group name
 		my ($groupID,$groupName) = split(/:/,$group);
 		if (!defined($groupID) || int($groupID) < 1) {
-			$logger->log(LOG_WARN,"[CONFIGMANAGER] Traffic group definition '$group' has invalid ID, ignoring");
+			$logger->log(LOG_WARN,"[CONFIGMANAGER] Traffic group definition '%s' has invalid ID, ignoring",$group);
 			next;
 		}
 		if (!defined($groupName) || $groupName eq "") {
-			$logger->log(LOG_WARN,"[CONFIGMANAGER] Traffic group definition '$group' has invalid name, ignoring");
+			$logger->log(LOG_WARN,"[CONFIGMANAGER] Traffic group definition '%s' has invalid name, ignoring",$group);
 			next;
 		}
 		$config->{'groups'}->{$groupID} = $groupName;
-		$logger->log(LOG_INFO,"[CONFIGMANAGER] Loaded traffic group '$groupName' with ID $groupID.");
+		$logger->log(LOG_INFO,"[CONFIGMANAGER] Loaded traffic group '%s' with ID %s.",$groupName,$groupID);
 	}
-	$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Traffic groups loaded.");
+	$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Traffic groups loaded");
 
 
 	# Split off traffic classes
@@ -367,17 +485,17 @@ sub plugin_init
 		# Split off class ID and class name
 		my ($classID,$className) = split(/:/,$class);
 		if (!defined(isNumber($classID))) {
-			$logger->log(LOG_WARN,"[CONFIGMANAGER] Traffic class definition '$class' has invalid ID, ignoring");
+			$logger->log(LOG_WARN,"[CONFIGMANAGER] Traffic class definition '%s' has invalid ID, ignoring",$class);
 			next;
 		}
 		if (!defined($className) || $className eq "") {
-			$logger->log(LOG_WARN,"[CONFIGMANAGER] Traffic class definition '$class' has invalid name, ignoring");
+			$logger->log(LOG_WARN,"[CONFIGMANAGER] Traffic class definition '%s' has invalid name, ignoring",$class);
 			next;
 		}
 		$config->{'classes'}->{$classID} = $className;
-		$logger->log(LOG_INFO,"[CONFIGMANAGER] Loaded traffic class '$className' with ID $classID.");
+		$logger->log(LOG_INFO,"[CONFIGMANAGER] Loaded traffic class '%s' with ID %s",$className,$classID);
 	}
-	$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Traffic classes loaded.");
+	$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Traffic classes loaded");
 
 
 	# Load interfaces
@@ -405,7 +523,9 @@ sub plugin_init
 			$config->{'interfaces'}->{$interface}->{'name'} = $iconfig->{'name'};
 		} else {
 			$config->{'interfaces'}->{$interface}->{'name'} = "$interface (auto)";
-			$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Interface '$interface' has no 'name' attribute, using '$interface (auto)'");
+			$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Interface '%s' has no 'name' attribute, using '%s (auto)'",
+					$interface,$interface
+			);
 		}
 
 		# Check our interface rate
@@ -414,11 +534,13 @@ sub plugin_init
 			if (defined(my $rate = isNumber($iconfig->{'rate'}))) {
 				$config->{'interfaces'}->{$interface}->{'rate'} = $rate;
 			} else {
-				$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface '$interface' has invalid 'rate' attribute, using 100000 instead");
+				$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface '%s' has invalid 'rate' attribute, using 100000 instead",
+						$interface
+				);
 			}
 		} else {
 			$config->{'interfaces'}->{$interface}->{'rate'} = 100000;
-			$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Interface '$interface' has no 'rate' attribute specified, using 100000");
+			$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Interface '%s' has no 'rate' attribute specified, using 100000",$interface);
 		}
 
 
@@ -442,11 +564,20 @@ sub plugin_init
 
 
 				if (!defined(isNumber($iclassID))) {
-					$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface '$interface' class definition '$iclass' has invalid Class ID, ignoring definition");
+					$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface '%s' class definition '%s' has invalid Class ID, ignoring ".
+							"definition",
+							$interface,
+							$iclass
+					);
 					next;
 				}
 				if (!defined($config->{'classes'}->{$iclassID})) {
-					$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface '$interface' class definition '$iclass' uses Class ID '$iclassID' which doesn't exist");
+					$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface '%s' class definition '%s' uses Class ID '%s' which doesn't ".
+							"exist",
+							$interface,
+							$iclass,
+							$iclassID
+					);
 					next;
 				}
 
@@ -462,11 +593,17 @@ sub plugin_init
 							$iclassCIR = $cir;
 						}
 					} else {
-						$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface '$interface' class '$iclassID' has invalid CIR, ignoring definition");
+						$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface '%s' class '%s' has invalid CIR, ignoring definition",
+								$interface,
+								$iclassID
+						);
 						next;
 					}
 				} else {
-					$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface '$interface' class '$iclassID' has missing CIR, ignoring definition");
+					$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface '%s' class '%s' has missing CIR, ignoring definition",
+							$interface,
+							$iclassID
+					);
 					next;
 				}
 
@@ -482,30 +619,53 @@ sub plugin_init
 							$iclassLimit = $Limit;
 						}
 					} else {
-						$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface '$interface' class '$iclassID' has invalid Limit, ignoring");
+						$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface '%s' class '%s' has invalid Limit, ignoring",
+								$interface,
+								$iclassID
+						);
 						next;
 					}
 				} else {
-					$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Interface '$interface' class '$iclassID' has missing Limit, using CIR '$iclassCIR' instead",
-							$config->{'interfaces'}->{$interface}->{'rate'});
+					$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Interface '%s' class '%s' has missing Limit, using CIR '%s' instead",
+							$interface,
+							$iclassID,
+							$iclassCIR
+					);
 					$iclassLimit = $iclassCIR;
 				}
 
 				# Check if rates are below are sane
 				if ($iclassCIR > $config->{'interfaces'}->{$interface}->{'rate'}) {
-					$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface '$interface' class '$iclassID' has CIR '$iclassCIR' > interface speed '%s', adjusting to '%s'",
+					$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface '%s' class '%s' has CIR '%s' > interface speed '%s', ".
+							"adjusting to '%s'",
+							$interface,
+							$iclassID,
+							$iclassCIR,
 							$iclassCIR > $config->{'interfaces'}->{$interface}->{'rate'},
-							$iclassCIR > $config->{'interfaces'}->{$interface}->{'rate'});
+							$iclassCIR > $config->{'interfaces'}->{$interface}->{'rate'}
+					);
 					$iclassCIR = $iclassCIR > $config->{'interfaces'}->{$interface}->{'rate'};
 				}
 				if ($iclassLimit > $config->{'interfaces'}->{$interface}->{'rate'}) {
-					$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface '$interface' class '$iclassID' has Limit '$iclassLimit' > interface speed '%s', adjusting to '%s'",
+					$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface '%s' class '%s' has Limit '%s' > interface speed '%s', ".
+							"adjusting to '%s'",
+							$interface,
+							$iclassID,
+							$iclassCIR,
 							$iclassCIR > $config->{'interfaces'}->{$interface}->{'rate'},
-							$iclassCIR > $config->{'interfaces'}->{$interface}->{'rate'});
+							$iclassCIR > $config->{'interfaces'}->{$interface}->{'rate'}
+					);
 					$iclassLimit = $iclassCIR > $config->{'interfaces'}->{$interface}->{'rate'};
 				}
 				if ($iclassCIR > $iclassLimit) {
-					$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface '$interface' class '$iclassID' has CIR '$iclassLimit' > Limit '$iclassLimit', adjusting CIR to '$iclassLimit");
+					$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface '%s' class '%s' has CIR '%s' > Limit '%s', adjusting CIR ".
+							"to '%s'",
+							$interface,
+							$iclassID,
+							$iclassLimit,
+							$iclassLimit,
+							$iclassLimit
+					);
 					$iclassCIR = $iclassLimit;
 				}
 
@@ -515,14 +675,22 @@ sub plugin_init
 					'limit' => $iclassLimit
 				};
 
-				$logger->log(LOG_INFO,"[CONFIGMANAGER] Loaded interface '$interface' class rate for class ID '$iclassID': $iclassCIR/$iclassLimit.");
+				$logger->log(LOG_INFO,"[CONFIGMANAGER] Loaded interface '%s' class rate for class ID '%s': %s/%s",
+						$interface,
+						$iclassID,
+						$iclassCIR,
+						$iclassLimit
+				);
 			}
 
 			# Time to check the interface classes
 			foreach my $classID (keys %{$config->{'classes'}}) {
 				# Check if we have a rate defined for this class in the interface definition
 				if (!defined($config->{'interfaces'}->{$interface}->{'classes'}->{$classID})) {
-					$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface '$interface' has no class '$classID' defined, using interface limit");
+					$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface '%s' has no class '%s' defined, using interface limit",
+							$interface,
+							$classID
+					);
 					$config->{'interfaces'}->{$interface}->{'classes'}->{$classID} = {
 						'cir' => $config->{'interfaces'}->{$interface}->{'rate'},
 						'limit' => $config->{'interfaces'}->{$interface}->{'rate'}
@@ -532,7 +700,7 @@ sub plugin_init
 		}
 
 	}
-	$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Loading interfaces completed.");
+	$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Loading interfaces completed");
 
 	# Pull in interface groupings
 	$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Loading interface groups...");
@@ -555,15 +723,23 @@ sub plugin_init
 		# Split off class ID and class name
 		my ($txiface,$rxiface,$friendlyName) = split(/[:,]/,$interfaceGroup);
 		if (!defined($config->{'interfaces'}->{$txiface})) {
-			$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface group definition '$interfaceGroup' has invalid interface '$txiface', ignoring");
+			$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface group definition '%s' has invalid interface '%s', ignoring",
+					$interfaceGroup,
+					$txiface
+			);
 			next;
 		}
 		if (!defined($config->{'interfaces'}->{$rxiface})) {
-			$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface group definition '$interfaceGroup' has invalid interface '$rxiface', ignoring");
+			$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface group definition '%s' has invalid interface '%s', ignoring",
+					$interfaceGroup,
+					$rxiface
+			);
 			next;
 		}
 		if (!defined($friendlyName) || $friendlyName eq "") {
-			$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface group definition '$interfaceGroup' has invalid friendly name, ignoring");
+			$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface group definition '%s' has invalid friendly name, ignoring",
+					$interfaceGroup,
+			);
 			next;
 		}
 
@@ -573,9 +749,20 @@ sub plugin_init
 			'rxiface' => $rxiface
 		};
 
-		$logger->log(LOG_INFO,"[CONFIGMANAGER] Loaded interface group '$friendlyName' with interfaces '$txiface/$rxiface'.");
+		$logger->log(LOG_INFO,"[CONFIGMANAGER] Loaded interface group '%s' with interfaces '%s/%s'",
+				$friendlyName,
+				$txiface,
+				$rxiface
+		);
+	}
+
+	# Initialize IP address map
+	foreach my $interfaceGroupID (keys %{$config->{'interface_groups'}}) {
+		# Blank interface IP address map for interface group
+		$interfaceIPMap->{$interfaceGroupID} = { };
 	}
-	$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Interface groups loaded.");
+
+	$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Interface groups loaded");
 
 
 	# Check if we using a default pool or not
@@ -583,10 +770,15 @@ sub plugin_init
 		# Check if its a number
 		if (defined(my $default_pool = isNumber($gconfig->{'default_pool'}))) {
 			if (defined($config->{'classes'}->{$default_pool})) {
-				$logger->log(LOG_INFO,"[CONFIGMANAGER] Default pool set to use class '$default_pool' (%s)",$config->{'classes'}->{$default_pool});
+				$logger->log(LOG_INFO,"[CONFIGMANAGER] Default pool set to use class '%s' (%s)",
+						$default_pool,
+						$config->{'classes'}->{$default_pool}
+				);
 				$config->{'default_pool'} = $default_pool;
 			} else {
-				$logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot enable default pool, class '$default_pool' does not exist");
+				$logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot enable default pool, class '%s' does not exist",
+						$default_pool
+				);
 			}
 		} else {
 			$logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot enable default pool, value for 'default_pool' is invalid");
@@ -596,24 +788,31 @@ sub plugin_init
 	# Check if we have a state file
 	if (defined(my $statefile = $globals->{'file.config'}->{'system'}->{'statefile'})) {
 		$config->{'statefile'} = $statefile;
-		$logger->log(LOG_INFO,"[CONFIGMANAGER] Set statefile to '$statefile'");
+		$logger->log(LOG_INFO,"[CONFIGMANAGER] Set statefile to '%s'",$statefile);
 	}
 
 	# This is our configuration processing session
 	POE::Session->create(
 		inline_states => {
-			_start => \&session_start,
-			_stop => \&session_stop,
+			_start => \&_session_start,
+			_stop => \&_session_stop,
+			_tick => \&_session_tick,
+			_SIGHUP => \&_session_SIGHUP,
+
+			limit_add => \&_session_limit_add,
 
-			tick => \&session_tick,
+			override_add => \&_session_override_add,
+			override_change => \&_session_override_change,
+			override_remove => \&_session_override_remove,
 
-			process_limit_change => \&process_limit_change,
-			process_limit_remove => \&process_limit_remove,
+			pool_add => \&_session_pool_add,
+			pool_remove => \&_session_pool_remove,
+			pool_change => \&_session_pool_change,
 
-			process_override_change => \&process_override_change,
-			process_override_remove => \&process_override_remove,
+			poolmember_add => \&_session_poolmember_add,
+			poolmember_remove => \&_session_poolmember_remove,
+			poolmember_change => \&_session_poolmember_change,
 
-			handle_SIGHUP => \&handle_SIGHUP,
 		}
 	);
 }
@@ -622,13 +821,17 @@ sub plugin_init
 # Start the plugin
 sub plugin_start
 {
-	$logger->log(LOG_INFO,"[CONFIGMANAGER] Started with ".( keys %{$limitChangeQueue} )." queued items");
+	$logger->log(LOG_INFO,"[CONFIGMANAGER] Started with %s pools, %s pool members and %s overrides",
+			scalar(keys %{$pools}),
+			scalar(keys %{$poolMembers}),
+			scalar(keys %{$overrides})
+	);
 }
 
 
 
 # Initialize config manager
-sub session_start
+sub _session_start
 {
 	my ($kernel,$heap) = @_[KERNEL,HEAP];
 
@@ -640,20 +843,20 @@ sub session_start
 	if (-f $config->{'statefile'}) {
 		_load_statefile($kernel);
 	} else {
-		$logger->log(LOG_WARN,"[CONFIGMANAGER] Statefile '$config->{'statefile'}' cannot be opened: $!");
+		$logger->log(LOG_WARN,"[CONFIGMANAGER] Statefile '%s' cannot be opened: %s",$config->{'statefile'},$!);
 	}
 
 	# Set delay on config updates
-	$kernel->delay(tick => TICK_PERIOD);
+	$kernel->delay('_tick' => TICK_PERIOD);
 
-	$kernel->sig('HUP', 'handle_SIGHUP');
+	$kernel->sig('HUP', '_SIGHUP');
 
 	$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Initialized");
 }
 
 
 # Stop the session
-sub session_stop
+sub _session_stop
 {
 	my ($kernel,$heap) = @_[KERNEL,HEAP];
 
@@ -668,17 +871,20 @@ sub session_stop
 
 	# Blow away all data
 	$globals = undef;
-	$limitChangeQueue = { };
-	$limits = { };
-	$limitIPMap = { };
-	$limitIDMap = { };
-	$limitIDCounter = 1;
-
-	$overrideChangeQueue = { };
-	$overrides = { };
-	$overrideMap = { };
-	$overrideIDMap = { };
-	$overrideIDCounter = 1;
+
+	$interfaceIPMap = { };
+
+	$pools = { };
+	$poolIdentifierMap = { };
+	$poolIDCounter = 1;
+
+	$poolMembers = { };
+	$poolMemberIDCounter = 1;
+	$poolMemberMap = { };
+
+	$poolChangeQueue = { };
+	$poolMemberChangeQueue = { };
+
 	# XXX: Blow away rest? config?
 
 	$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Shutdown");
@@ -688,397 +894,793 @@ sub session_stop
 
 
 # Time ticker for processing changes
-sub session_tick
+sub _session_tick
 {
 	my $kernel = $_[KERNEL];
 
+
 	my $now = time();
-	my $overridesChanged = 0;
 
-	# Process queues
-	if (_process_override_change_queue($kernel)) {
-		$overridesChanged++;
+	# Check if we should sync state to disk
+	if ($stateChanged && $lastStateSync + STATE_SYNC_INTERVAL < $now) {
+		_write_statefile();
 	}
-	_process_limit_change_queue($kernel);
 
-	# Check if we must run cleanups
+	# Check if we should cleanup
 	if ($lastCleanup + CLEANUP_INTERVAL < $now) {
-		_process_limit_cleanup($kernel);
-		# Bump up overridesChanged if something changed
-		if (_process_override_cleanup($kernel)) {
-			$overridesChanged++;
+		# Loop with all overrides and check for expired entries
+		while (my ($oid, $override) = each(%{$overrides})) {
+			# Override has effectively expired
+			if (defined($override->{'Expires'}) && $override->{'Expires'} > 0 && $override->{'Expires'} < $now) {
+				$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Override '%s' [%s] has expired, removing",
+						$override->{'FriendlyName'},
+						$oid
+				);
+				removeOverride($oid);
+			}
+		}
+		# Loop with all pool members and check for expired entries
+		while (my ($pmid, $poolMember) = each(%{$poolMembers})) {
+			# Pool member has effectively expired
+			if (defined($poolMember->{'Expires'}) && $poolMember->{'Expires'} > 0 && $poolMember->{'Expires'} < $now) {
+				$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Pool member '%s' [%s] has expired, removing",
+						$poolMember->{'Username'},
+						$pmid
+				);
+				removePoolMember($pmid);
+			}
+		}
+		# Loop with all the pools and check for expired entries
+		while (my ($pid, $pool) = each(%{$pools})) {
+			# Pool has effectively expired
+			if (defined($pool->{'Expires'}) && $pool->{'Expires'} > 0 && $pool->{'Expires'} < $now) {
+				# There are no members, its safe to remove
+				if (getPoolMembers($pid) == 0) {
+					$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Pool '%s' [%s] has expired, removing",
+							$pool->{'Identifier'},
+							$pid
+					);
+					removePool($pid);
+				}
+			}
 		}
+		# Reset last cleanup time
 		$lastCleanup = $now;
 	}
 
-	# If overrides changed, we need to reprocess all limits
-	if ($overridesChanged) {
-		_resolve_overrides_and_post($kernel);
-	}
+	# Loop through pool change queue
+	while (my ($pid, $pool) = each(%{$poolChangeQueue})) {
 
-	# Check if we should sync state to disk
-	if ($stateChanged && $lastStateSync + STATE_SYNC_INTERVAL < $now) {
-		_write_statefile();
-	}
+		my $shaperState = getPoolShaperState($pool->{'ID'});
 
-	# Reset tick
-	$kernel->delay(tick => TICK_PERIOD);
-}
+		# Pool is newly added
+		if ($pool->{'Status'} == CFGM_NEW) {
 
-# Process limit change
-sub process_limit_change
-{
-	my ($kernel, $limit) = @_[KERNEL, ARG0];
+			# If the change is not yet live, we should queue it to go live
+			if ($shaperState == SHAPER_NOTLIVE) {
+				$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Pool '%s' [%s] new and not live, adding to shaper",
+						$pool->{'Identifier'},
+						$pid
+				);
+				$kernel->post('shaper' => 'pool_add' => $pid);
+				# Set pending online
+				setPoolShaperState($pool->{'ID'},SHAPER_PENDING);
+				$pool->{'Status'} = CFGM_ONLINE;
+				# Remove from queue
+				delete($poolChangeQueue->{$pid});
+			} else {
+				$logger->log(LOG_ERR,"[CONFIGMANAGER] Pool '%s' [%s] has UNKNOWN state (CFGM_NEW && !SHAPER_NOTLIVE)");
+			}
 
-	_process_limit_change($limit);
-	_process_limit_change_queue($kernel);
-}
+		# Pool is online but NOTLIVE
+		} elsif ($pool->{'Status'} == CFGM_ONLINE) {
+
+			# We've transitioned more than likely from offline, any state to online
+			# We don't care if the shaper is pending removal, we going to force re-adding now
+			if ($shaperState != SHAPER_LIVE) {
+				$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Pool '%s' [%s] online and not in live state, re-queue as add",
+						$pool->{'Identifier'},
+						$pid
+				);
+				$pool->{'Status'} = CFGM_NEW;
+			} else {
+				$logger->log(LOG_ERR,"[CONFIGMANAGER] Pool '%s' [%s] has UNKNOWN state (CFGM_ONLINE && SHAPER_LIVE)",
+						$pool->{'Identifier'},
+						$pid
+				);
+			}
 
-# Process limit remove
-sub process_limit_remove
-{
-	my ($kernel, $limit) = @_[KERNEL, ARG0];
 
-	_process_limit_remove($kernel,$limit);
-	_process_limit_change_queue($kernel);
-}
+		# Pool has been modified
+		} elsif ($pool->{'Status'} == CFGM_CHANGED) {
+
+			# If the shaper is live we can go ahead
+			if ($shaperState == SHAPER_LIVE) {
+				$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Pool '%s' [%s] has been modified, sending to shaper",
+						$pool->{'Identifier'},
+						$pid
+				);
+				$kernel->post('shaper' => 'pool_change' => $pid);
+				# Set pending online
+				setPoolShaperState($pool->{'ID'},SHAPER_PENDING);
+				$pool->{'Status'} = CFGM_ONLINE;
+				# Remove from queue
+				delete($poolChangeQueue->{$pid});
+
+			} elsif ($shaperState == SHAPER_NOTLIVE) {
+				$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Pool '%s' [%s] has been modified but not live, re-queue as add",
+						$pool->{'Identifier'},
+						$pid
+				);
+				$pool->{'Status'} = CFGM_NEW;
 
-# Process override change
-sub process_override_change
-{
-	my ($kernel, $override) = @_[KERNEL, ARG0];
+			} else {
+				$logger->log(LOG_ERR,"[CONFIGMANAGER] Pool '%s' [%s] has UNKNOWN state (CFGM_CHANGED && !SHAPER_LIVE && ".
+						"!SHAPER_NOTLIVE)",
+						$pool->{'Identifier'},
+						$pid
+				);
+			}
 
-	_process_override_change($override);
-	# If something changed, reprocess the limits
-	if (_process_override_change_queue($kernel)) {
-		_resolve_overrides_and_post($kernel);
-	}
-}
 
-# Process override remove
-sub process_override_remove
-{
-	my ($kernel, $override) = @_[KERNEL, ARG0];
+		# Pool is being removed?
+		} elsif ($pool->{'Status'} == CFGM_OFFLINE) {
 
-	_process_override_remove($override);
-	_process_override_change_queue($kernel);
-	_resolve_overrides_and_post($kernel);
-}
+			# If the change is live, but should go offline, queue it
+			if ($shaperState == SHAPER_LIVE) {
 
+				if ($now - $pool->{'LastUpdate'} > 30) {
+					# If we still have pool members, we got to abort
+					if (!getPoolMembers($pid)) {
+						$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Pool '%s' [%s] marked offline and stale, removing from shaper",
+								$pool->{'Identifier'},
+								$pid
+						);
+						$kernel->post('shaper' => 'pool_remove' => $pid);
+						setPoolShaperState($pool->{'ID'},SHAPER_PENDING);
+					} else {
+						$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Pool '%s' [%s] marked offline, but still has pool members, ".
+								"aborting remove",
+								$pool->{'Identifier'},
+								$pid
+						);
+						$pool->{'Status'} = CFGM_ONLINE;
+						delete($poolChangeQueue->{$pid});
+					}
 
-# Function to check the group ID exists
-sub checkGroupID
-{
-	my $gid = shift;
+				} else {
+					# Try remove all our pool members
+					if (my @poolMembers = getPoolMembers($pid)) {
+						# Loop with members and remove
+						foreach my $pmid (@poolMembers) {
+							my $poolMember = $poolMembers->{$pmid};
+							# Only remove ones online
+							if ($poolMember->{'Status'} == CFGM_ONLINE) {
+								$logger->log(LOG_INFO,"[CONFIGMANAGER] Pool '%s' [%s] marked offline and fresh, removing pool ".
+										"member [%s]",
+										$pool->{'Identifier'},
+										$pid,
+										$pmid
+								);
+								removePoolMember($pmid);
+							}
+						}
+					} else {
+						$logger->log(LOG_INFO,"[CONFIGMANAGER] Pool '%s' [%s] marked offline and fresh, postponing",
+								$pool->{'Identifier'},
+								$pid
+						);
+					}
+				}
 
+			} elsif ($shaperState == SHAPER_NOTLIVE) {
+				$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Pool '%s' [%s] marked offline and is not live, removing",
+						$pool->{'identifier'},
+						$pid
+				);
+				# Remove pool from identifier map
+				delete($poolIdentifierMap->{$pool->{'InterfaceGroupID'}}->{$pool->{'Identifier'}});
+				# Remove pool member mapping
+				delete($poolMemberMap->{$pool->{'ID'}});
+				# Remove from queue
+				delete($poolChangeQueue->{$pid});
+				# Cleanup overrides
+				_override_remove_pool($pool->{'ID'});
+				# Remove pool
+				delete($pools->{$pool->{'ID'}});
+			}
 
-	if (defined($config->{'groups'}->{$gid})) {
-		return $gid;
+		} else {
+			$logger->log(LOG_ERR,"[CONFIGMANAGER] Pool '%s' [%s] has UNKNOWN state '%s'",
+					$pool->{'Identifier'},
+					$pid,
+					$pool->{'Status'}
+			);
+		}
 	}
 
-	return undef;
-}
+	# Loop through pool member change queue
+	while (my ($pmid, $poolMember) = each(%{$poolMemberChangeQueue})) {
+
+		my $pool = $pools->{$poolMember->{'PoolID'}};
+		my $shaperState = getPoolMemberShaperState($poolMember->{'ID'});
+
+		# Pool is newly added
+		if ($poolMember->{'Status'} == CFGM_NEW) {
+
+			# If the change is not yet live, we should queue it to go live
+			if ($shaperState == SHAPER_NOTLIVE) {
+				$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Pool '%s' member '%s' [%s] new and not live, adding to shaper",
+						$pool->{'Identifier'},
+						$poolMember->{'IPAddress'},
+						$pmid
+				);
+				$kernel->post('shaper' => 'poolmember_add' => $pmid);
+				# Set pending online
+				setPoolMemberShaperState($poolMember->{'ID'},SHAPER_PENDING);
+				$poolMember->{'Status'} = CFGM_ONLINE;
+				# Remove from queue
+				delete($poolMemberChangeQueue->{$pmid});
+			} else {
+				$logger->log(LOG_ERR,"[CONFIGMANAGER] Pool '%s' member '%s' [%s] has UNKNOWN state (CFGM_NEW && !SHAPER_NOTLIVE)",
+						$pool->{'Identifier'},
+						$poolMember->{'IPAddress'},
+						$pmid
+				);
+			}
 
+		# Pool member is online but NOTLIVE
+		} elsif ($poolMember->{'Status'} == CFGM_ONLINE) {
+
+			# We've transitioned more than likely from offline, any state to online
+			# We don't care if the shaper is pending removal, we going to force re-adding now
+			if ($shaperState != SHAPER_LIVE) {
+				$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Pool '%s' member '%s' [%s] online and not in live state, re-queue as add",
+						$pool->{'Identifier'},
+						$poolMember->{'IPAddress'},
+						$pmid
+				);
+				$poolMember->{'Status'} = CFGM_NEW;
+			} else {
+				$logger->log(LOG_ERR,"[CONFIGMANAGER] Pool '%s' member '%s' [%s] has UNKNOWN state (CFGM_ONLINE && SHAPER_LIVE)",
+						$pool->{'Identifier'},
+						$poolMember->{'IPAddress'},
+						$pmid
+				);
+			}
 
-# Function to check interface group ID
-sub checkInterfaceGroupID
-{
-	my $igid = shift;
 
+		# Pool member has been modified
+		} elsif ($poolMember->{'Status'} == CFGM_CHANGED) {
+
+			# If the shaper is live we can go ahead
+			if ($shaperState == SHAPER_LIVE) {
+				$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Pool '%s' member '%s' [%s] has been modified, sending to shaper",
+						$pool->{'Identifier'},
+						$poolMember->{'IPAddress'},
+						$pmid
+				);
+				$kernel->post('shaper' => 'poolmember_change' => $pmid);
+				# Set pending online
+				setPoolMemberShaperState($poolMember->{'ID'},SHAPER_PENDING);
+				$poolMember->{'Status'} = CFGM_ONLINE;
+				# Remove from queue
+				delete($poolMemberChangeQueue->{$pmid});
+
+			} elsif ($shaperState == SHAPER_NOTLIVE) {
+				$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Pool '%s' member '%s' [%s] has been modified but not live, re-queue as ".
+						"add",
+						$pool->{'Identifier'},
+						$poolMember->{'IPAddress'},
+						$pmid
+				);
+				$poolMember->{'Status'} = CFGM_NEW;
+			} else {
+				$logger->log(LOG_ERR,"[CONFIGMANAGER] Pool '%s' member '%s' [%s] has UNKNOWN state (CFGM_CHANGED && ".
+						"!SHAPER_LIVE && !SHAPER_NOTLIVE)",
+						$pool->{'Identifier'},
+						$poolMember->{'IPAddress'},
+						$pmid
+				);
+			}
 
-	if (defined($config->{'interface_groups'}->{$igid})) {
-		return $igid;
-	}
 
-	return undef;
-}
+		# Pool is being removed?
+		} elsif ($poolMember->{'Status'} == CFGM_OFFLINE) {
 
+			# If the change is live, but should go offline, queue it
+			if ($shaperState == SHAPER_LIVE) {
 
-# Function to check the match priority ID exists
-sub checkMatchPriorityID
-{
-	my $mpid = shift;
+				if ($now - $poolMember->{'LastUpdate'} > 10) {
+					$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Pool '%s' member '%s' [%s] marked offline and stale, removing from ".
+							"shaper",
+							$pool->{'Identifier'},
+							$poolMember->{'IPAddress'},
+							$pmid
+					);
+					$kernel->post('shaper' => 'poolmember_remove' => $pmid);
+					setPoolMemberShaperState($poolMember->{'ID'},SHAPER_PENDING);
+				} else {
+					$logger->log(LOG_INFO,"[CONFIGMANAGER] Pool '%s' member '%s' [%s] marked offline and fresh, postponing",
+							$pool->{'Identifier'},
+							$poolMember->{'IPAddress'},
+							$pmid
+					);
+				}
+
+			} elsif ($shaperState == SHAPER_NOTLIVE) {
+				$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Pool '%s' member '%s' [%s] marked offline and is not live, removing",
+						$pool->{'Identifier'},
+						$poolMember->{'IPAddress'},
+						$pmid
+				);
+				# Unlink interface IP address map
+				delete($interfaceIPMap->{$pool->{'InterfaceGroupID'}}->{$poolMember->{'IPAddress'}}->{$poolMember->{'ID'}});
+				# Unlink pool map
+				delete($poolMemberMap->{$pool->{'ID'}}->{$poolMember->{'ID'}});
+				# Remove from queue
+				delete($poolMemberChangeQueue->{$pmid});
+				# We need to re-process the overrides after the member has been removed
+				_override_resolve([$poolMember->{'PoolID'}]);
+				# Remove pool member
+				delete($poolMembers->{$poolMember->{'ID'}});
+			}
 
+		} else {
+			$logger->log(LOG_ERR,"[CONFIGMANAGER] Pool '%s' member '%s' [%s] has UNKNOWN state '%s'",
+					$pool->{'Identifier'},
+					$poolMember->{'IPAddress'},
+					$pmid,
+					$poolMember->{'Status'}
+			);
+		}
 
-	if (defined($config->{'match_priorities'}->{$mpid})) {
-		return $mpid;
 	}
 
-	return undef;
+	# Reset tick
+	$kernel->delay('_tick' => TICK_PERIOD);
 }
 
 
-# Function to check the class ID exists
-sub checkClassID
+# Handle SIGHUP
+sub _session_SIGHUP
 {
-	my $cid = shift;
-
-
-	if (defined($config->{'classes'}->{$cid})) {
-		return $cid;
-	}
+	my ($kernel, $heap, $signal_name) = @_[KERNEL, HEAP, ARG0];
 
-	return undef;
+	$logger->log(LOG_WARN,"[CONFIGMANAGER] Got SIGHUP, ignoring for now");
 }
 
 
-# Return interface classes
-sub getInterface
+# Event for 'pool_add'
+sub _session_pool_add
 {
-	my $interface = shift;
+	my ($kernel, $poolData) = @_[KERNEL, ARG0];
 
-	# If we have this interface return its classes
-	if (defined($config->{'interfaces'}->{$interface})) {
-		my $res = dclone($config->{'interfaces'}->{$interface});
-		# We don't really want to return classes
-		delete($res->{'classes'});
-		# And return it...
-		return $res;
-	}
 
-	return undef;
-}
+	if (!defined($poolData)) {
+		$logger->log(LOG_NOTICE,"[CONFIGMANAGER] No pool data provided for 'pool_add' event");
+		return;
+	}
 
+	# Check if we have all the attributes we need
+	my $isInvalid;
+	foreach my $attr (POOL_REQUIRED_ATTRIBUTES) {
+		if (!defined($poolData->{$attr})) {
+			$isInvalid = $attr;
+			last;
+		}
+	}
+	if ($isInvalid) {
+		$logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process pool add as there is an attribute missing: '%s'",
+				$isInvalid
+		);
+		return;
+	}
 
-# Function to return the configured Interfaces
-sub getInterfaces
-{
-	return [ keys %{$config->{'interfaces'}} ];
+	createPool($poolData);
 }
 
 
-# Return interface classes
-sub getInterfaceClasses
+# Event for 'pool_remove'
+sub _session_pool_remove
 {
-	my $interface = shift;
+	my ($kernel, $pid) = @_[KERNEL, ARG0];
 
 
-	# If we have this interface return its classes
-	if (defined($config->{'interfaces'}->{$interface})) {
-		return dclone($config->{'interfaces'}->{$interface}->{'classes'});
+	my $pool;
+	if (!defined(getPool($pid))) {
+		$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Invalid pool ID '%s' for 'pool_remove' event",prettyUndef($pid));
+		return;
 	}
 
-	return undef;
+	removePool($pid);
 }
 
 
-# Function to return our default pool configuration
-sub getInterfaceDefaultPool
+# Event for 'pool_change'
+sub _session_pool_change
 {
-	my $interface = shift;
+	my ($kernel, $poolData) = @_[KERNEL, ARG0];
 
 
-	# We don't really need the interface to return the default pool
-	return $config->{'default_pool'};
+	if (!isPoolIDValid($poolData->{'ID'})) {
+		$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Invalid pool ID '%s' for 'pool_change' event",prettyUndef($poolData->{'ID'}));
+		return;
+	}
+
+	changePool($poolData);
 }
 
 
-# Function to return interface rate
-sub getInterfaceRate
+# Event for 'poolmember_add'
+sub _session_poolmember_add
 {
-	my $interface = shift;
+	my ($kernel, $poolMemberData) = @_[KERNEL, ARG0];
 
 
-	# If we have this interface return its rate
-	if (defined($config->{'interfaces'}->{$interface})) {
-		return $config->{'interfaces'}->{$interface}->{'rate'};
+	if (!defined($poolMemberData)) {
+		$logger->log(LOG_NOTICE,"[CONFIGMANAGER] No pool member data provided for 'poolmember_add' event");
+		return;
 	}
 
-	return undef;
-}
-
-
-# Function to get interface groups
-sub getInterfaceGroups
-{
-	my $interface_groups = dclone($config->{'interface_groups'});
-
+	# Check if we have all the attributes we need
+	my $isInvalid;
+	foreach my $attr (POOLMEMBER_REQUIRED_ATTRIBUTES) {
+		if (!defined($poolMemberData->{$attr})) {
+			$isInvalid = $attr;
+			last;
+		}
+	}
+	if ($isInvalid) {
+		$logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process poolmember add as there is an attribute missing: '%s',$isInvalid");
+		return;
+	}
 
-	return $interface_groups;
+	createPoolMember($poolMemberData);
 }
 
 
-# Function to get an interface group
-sub getInterfaceGroup
+# Event for 'poolmember_remove'
+sub _session_poolmember_remove
 {
-	my $interfaceGroup = shift;
+	my ($kernel, $pmid) = @_[KERNEL, ARG0];
 
 
-	if (defined($config->{'interface_groups'}->{$interfaceGroup})) {
-		return dclone($config->{'interface_groups'}->{$interfaceGroup});
+	if (!isPoolMemberIDValid($pmid)) {
+		$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Invalid pool member ID '%s' for 'poolmember_remove' event",prettyUndef($pmid));
+		return;
 	}
 
-	return undef;
+	removePoolMember($pmid);
 }
 
 
-# Function to check if interface group is valid
-sub isInterfaceGroupValid
+# Event for 'poolmember_change'
+sub _session_poolmember_change
 {
-	my $interfaceGroup = shift;
+	my ($kernel, $poolMemberData) = @_[KERNEL, ARG0];
 
 
-	if (defined($interfaceGroup) && defined($config->{'interface_groups'}->{$interfaceGroup})) {
-		return $interfaceGroup;
+	if (!isPoolMemberIDValid($poolMemberData->{'ID'})) {
+		$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Invalid pool member ID '%s' for 'poolmember_change' event",
+				prettyUndef($poolMemberData->{'ID'})
+		);
+		return;
 	}
 
-	return undef;
+	changePoolMember($poolMemberData);
 }
 
 
-# Function to get match priorities
-sub getMatchPriorities
+# Event for 'limit_add'
+sub _session_limit_add
 {
-	my $match_priorities = dclone($config->{'match_priorities'});
+	my ($kernel, $limitData) = @_[KERNEL, ARG0];
 
 
-	return $match_priorities;
+	if (!defined($limitData)) {
+		$logger->log(LOG_NOTICE,"[CONFIGMANAGER] No limit data provided for 'limit_add' event");
+		return;
+	}
+
+	# Check if we have all the attributes we need
+	my $isInvalid;
+	foreach my $attr (LIMIT_REQUIRED_ATTRIBUTES) {
+		if (!defined($limitData->{$attr})) {
+			$isInvalid = $attr;
+			last;
+		}
+	}
+	if ($isInvalid) {
+		$logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process limit add as there is an attribute missing: '%s'",$isInvalid);
+		return;
+	}
+
+	createLimit($limitData);
 }
 
 
-# Function to check if interface group is valid
-sub isMatchPriorityValid
+# Event for 'override_add'
+sub _session_override_add
+{
+	my ($kernel, $overrideData) = @_[KERNEL, ARG0];
+
+
+	if (!defined($overrideData)) {
+		$logger->log(LOG_NOTICE,"[CONFIGMANAGER] No override data provided for 'override_add' event");
+		return;
+	}
+
+	# Check that we have at least one match attribute
+	my $isValid = 0;
+	foreach my $item (OVERRIDE_MATCH_ATTRIBUTES) {
+		$isValid++;
+	}
+	if (!$isValid) {
+		$logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process override as there is no selection attribute");
+		return;
+	}
+
+	createOverride($overrideData);
+}
+
+
+# Event for 'override_remove'
+sub _session_override_remove
 {
-	my $matchPriority = shift;
+	my ($kernel, $oid) = @_[KERNEL, ARG0];
 
 
-	if (defined($matchPriority) && defined($config->{'match_priorities'}->{$matchPriority})) {
-		return $matchPriority;
+	if (!isOverrideIDValid($oid)) {
+		$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Invalid override ID '%s' for 'override_remove' event",prettyUndef($oid));
+		return;
 	}
 
-	return undef;
+	removeOverride($oid);
 }
 
 
-# Function to check if the status is ok
-sub checkStatus
+# Event for 'override_change'
+sub _session_override_change
 {
-	my $status = shift;
+	my ($kernel, $overrideData) = @_[KERNEL, ARG0];
 
 
-	if ($status eq "new" || $status eq "offline" || $status eq "online" || $status eq "conflict" || $status eq "unknown") {
-		return $status
+	if (!isOverrideIDValid($overrideData->{'ID'})) {
+		$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Invalid override ID '%s' for 'override_change' event",
+				prettyUndef($overrideData->{'ID'})
+		);
+		return;
 	}
 
-	return undef;
+	changeOverride($overrideData);
 }
 
 
-# Function to return a limit username
-sub getLimitUsername
+# Function to check the group ID exists
+sub isGroupIDValid
 {
-	my $lid = shift;
+	my $gid = shift;
 
 
-	if (defined($limits->{$lid})) {
-		return $limits->{$lid}->{'Username'};
+	if (defined($config->{'groups'}->{$gid})) {
+		return $gid;
 	}
-	return undef;
+
+	return;
 }
 
 
-# Function to return a limit TX interface
-sub getLimitTxInterface
+# Function to check the class ID exists
+sub isClassIDValid
 {
-	my $lid = shift;
+	my $cid = shift;
 
 
-	if (defined($limits->{$lid})) {
-		return $config->{'interface_groups'}->{$limits->{$lid}->{'InterfaceGroupID'}}->{'txiface'};
+	if (!defined($config->{'classes'}->{$cid})) {
+		return;
 	}
 
-	return undef;
+	return $cid;
 }
 
 
-# Function to return a limit RX interface
-sub getLimitRxInterface
+# Function to return if an interface ID is valid
+sub isInterfaceIDValid
 {
-	my $lid = shift;
+	my $iid = shift;
 
 
-	if (defined($limits->{$lid})) {
-		return $config->{'interface_groups'}->{$limits->{$lid}->{'InterfaceGroupID'}}->{'rxiface'};
+	# Return undef if interface is not valid
+	if (!defined($config->{'interfaces'}->{$iid})) {
+		return;
 	}
 
-	return undef;
+	return $iid;
+}
+
+
+# Function to return the configured Interfaces
+sub getInterfaces
+{
+	return [ keys %{$config->{'interfaces'}} ];
 }
 
 
-# Function to return a limit match priority
-sub getLimitMatchPriority
+# Return interface classes
+sub getInterface
 {
-	my $lid = shift;
+	my $iid = shift;
 
 
-	if (defined($limits->{$lid})) {
-		# NK: No actual mappping yet
-		return $limits->{$lid}->{'MatchPriorityID'};
+	# If we have this interface return its classes
+	if (!isInterfaceIDValid($iid)) {
+		return;
 	}
 
-	return undef;
+	my $res = dclone($config->{'interfaces'}->{$iid});
+	# We don't really want to return classes
+	delete($res->{'classes'});
+	# And return it...
+	return $res;
 }
 
 
-# Function to return a limit
-sub getLimit
+# Return interface traffic classes
+sub getInterfaceTrafficClasses
 {
-	my $lid = shift;
+	my $iid = shift;
 
 
-	if (defined($limits->{$lid})) {
-		my %limit = %{$limits->{$lid}};
-		return \%limit;
+	# If we have this interface return its classes
+	if (!isInterfaceIDValid($iid)) {
+		return;
+	}
+
+	return dclone($config->{'interfaces'}->{$iid}->{'classes'});
+}
+
+
+# Function to return our default pool configuration
+sub getInterfaceDefaultPool
+{
+	my $interface = shift;
+
+
+	# We don't really need the interface to return the default pool
+	return $config->{'default_pool'};
+}
+
+
+# Function to return interface rate
+sub getInterfaceRate
+{
+	my $iid = shift;
+
+
+	# If we have this interface return its classes
+	if (!isInterfaceIDValid($iid)) {
+		return;
+	}
+
+	return $config->{'interfaces'}->{$iid}->{'rate'};
+}
+
+
+# Function to get interface groups
+sub getInterfaceGroups
+{
+	my $interface_groups = dclone($config->{'interface_groups'});
+
+
+	return $interface_groups;
+}
+
+
+# Function to check if interface group is valid
+sub isInterfaceGroupIDValid
+{
+	my $igid = shift;
+
+
+	if (!defined($igid) || !defined($config->{'interface_groups'}->{$igid})) {
+		return;
+	}
+
+	return $igid;
+}
+
+
+# Function to get an interface group
+sub getInterfaceGroup
+{
+	my $igid = shift;
+
+
+	if (!isInterfaceGroupIDValid($igid)) {
+		return;
 	}
 
-	return undef;
+	return dclone($config->{'interface_groups'}->{$igid});
 }
 
 
-# Function to return a list of limit ID's
-sub getLimits
+# Function to get match priorities
+sub getMatchPriorities
 {
-	return (keys %{$limits});
+	return dclone($config->{'match_priorities'});
 }
 
 
-# Function to set a limit attribute
-sub setLimitAttribute
+# Function to check if interface group is valid
+sub isMatchPriorityIDValid
 {
-	my ($lid,$attr,$value) = @_;
+	my $mpid = shift;
 
 
-	# Only set it if it exists
-	if (defined($lid) && defined($limits->{$lid})) {
-		$limits->{$lid}->{'attributes'}->{$attr} = $value;
+	# Check all is ok
+	if (!defined($mpid) || !defined($config->{'match_priorities'}->{$mpid})) {
+		return;
 	}
+
+	return $mpid;
 }
 
 
-# Function to get a limit attribute
-sub getLimitAttribute
+# Function to set a pool attribute
+sub setPoolAttribute
 {
-	my ($lid,$attr) = @_;
+	my ($pid,$attr,$value) = @_;
 
 
+	# Return if it doesn't exist
+	if (!isPoolIDValid($pid)) {
+		return;
+	}
+
+	$pools->{$pid}->{'.attributes'}->{$attr} = $value;
+
+	return $value;
+}
+
+
+# Function to get a pool attribute
+sub getPoolAttribute
+{
+	my ($pid,$attr) = @_;
+
+
+	# Return if it doesn't exist
+	if (!isPoolIDValid($pid)) {
+		return;
+	}
+
 	# Check if attribute exists first
-	if (defined($lid) && defined($limits->{$lid}) && defined($limits->{$lid}->{'attributes'}) && defined($limits->{$lid}->{'attributes'}->{$attr})) {
-		return $limits->{$lid}->{'attributes'}->{$attr};
+	if (!defined($pools->{$pid}->{'.attributes'}) || !defined($pools->{$pid}->{'.attributes'}->{$attr})) {
+		return;
 	}
-	return undef;
+
+	return $pools->{$pid}->{'.attributes'}->{$attr};
 }
 
 
-# Function to remove a limit attribute
-sub removeLimitAttribute
+# Function to remove a pool attribute
+sub removePoolAttribute
 {
-	my ($lid,$attr) = @_;
+	my ($pid,$attr) = @_;
+
 
+	# Return if it doesn't exist
+	if (!isPoolIDValid($pid)) {
+		return;
+	}
 
 	# 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});
+	if (!defined($pools->{$pid}->{'.attributes'}) || !defined($pools->{$pid}->{'.attributes'}->{$attr})) {
+		return;
 	}
+
+	return delete($pools->{$pid}->{'.attributes'}->{$attr});
 }
 
 
@@ -1087,808 +1689,1248 @@ sub getOverride
 {
 	my $oid = shift;
 
-	if (defined($oid) && defined($overrides->{$oid})) {
-		my %override = %{$overrides->{$oid}};
-		return \%override;
+
+	if (!isOverrideIDValid($oid)) {
+		return;
 	}
 
-	return undef;
+	my $override = dclone($overrides->{$oid});
+
+	return $override;
 }
 
 
-# Function to return a list of override ID's
+## Function to return a list of override ID's
 sub getOverrides
 {
 	return (keys %{$overrides});
 }
 
 
-# Function to set shaper state on a limit
-sub setShaperState
+# Function to create a pool
+sub createPool
 {
-	my ($lid,$state) = @_;
+	my $poolData = shift;
 
 
-	if (defined($lid) && defined($limits->{$lid})) {
-		$limits->{$lid}->{'_shaper.state'} = $state;
+	# Check if we have all the attributes we need
+	my $isInvalid;
+	foreach my $attr (POOL_REQUIRED_ATTRIBUTES) {
+		if (!defined($poolData->{$attr})) {
+			$isInvalid = $attr;
+			last;
+		}
+	}
+	if ($isInvalid) {
+		$logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process pool add as there is an attribute missing: '%s'",$isInvalid);
+		return;
 	}
 
-	return undef;
+	my $pool;
+
+	my $now = time();
+
+	# Now check if the identifier is valid
+	if (!defined($pool->{'Identifier'} = $poolData->{'Identifier'})) {
+		$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Cannot process pool add as Identifier is invalid");
+		return;
+	}
+	# Check interface group ID is OK
+	if (!defined($pool->{'InterfaceGroupID'} = isInterfaceGroupIDValid($poolData->{'InterfaceGroupID'}))) {
+		$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Cannot process pool add for '%s' as the InterfaceGroupID is invalid",
+				$pool->{'Identifier'}
+		);
+		return;
+	}
+	# If we already have this identifier added, return it as the pool
+	if (defined(my $pool = $poolIdentifierMap->{$pool->{'InterfaceGroupID'}}->{$pool->{'Identifier'}})) {
+		return $pool->{'ID'};
+	}
+	# Check class is OK
+	if (!defined($pool->{'ClassID'} = isClassIDValid($poolData->{'ClassID'}))) {
+		$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Cannot process pool add for '%s' as the ClassID is invalid",
+				$pool->{'Identifier'}
+		);
+		return;
+	}
+	# Make sure things are not attached to the default pool
+	if (defined($config->{'default_pool'}) && $pool->{'ClassID'} eq $config->{'default_pool'}) {
+		$logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process pool add for '%s' as the ClassID is the 'default_pool' ClassID",
+				$pool->{'Identifier'}
+		);
+		return;
+	}
+	# Check traffic limits
+	if (!isNumber($pool->{'TrafficLimitTx'} = $poolData->{'TrafficLimitTx'})) {
+		$logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process pool add for '%s' as the TrafficLimitTx is invalid",
+				$pool->{'Identifier'}
+		);
+		return;
+	}
+	if (!isNumber($pool->{'TrafficLimitRx'} = $poolData->{'TrafficLimitRx'})) {
+		$logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process pool add for '%s' as the TrafficLimitRx is invalid",
+				$pool->{'Identifier'}
+		);
+		return;
+	}
+	# If we don't have burst limits, improvize
+	if (!defined($pool->{'TrafficLimitTxBurst'} = $poolData->{'TrafficLimitTxBurst'})) {
+		$pool->{'TrafficLimitTxBurst'} = $pool->{'TrafficLimitTx'};
+		$pool->{'TrafficLimitTx'} = int($pool->{'TrafficLimitTxBurst'}/4);
+	}
+	if (!defined($pool->{'TrafficLimitRxBurst'} = $poolData->{'TrafficLimitRxBurst'})) {
+		$pool->{'TrafficLimitRxBurst'} = $pool->{'TrafficLimitRx'};
+		$pool->{'TrafficLimitRx'} = int($pool->{'TrafficLimitRxBurst'}/4);
+	}
+	# Set source
+	$pool->{'Source'} = $poolData->{'Source'};
+	# Set when this entry was created
+	$pool->{'Created'} = defined($poolData->{'Created'}) ? $poolData->{'Created'} : $now;
+	$pool->{'LastUpdate'} = $now;
+	# Set when this entry expires
+	$pool->{'Expires'} = defined($poolData->{'Expires'}) ? int($poolData->{'Expires'}) : 0;
+	# Check status is OK
+	$pool->{'Status'} = CFGM_NEW;
+	# Set friendly name and notes
+	$pool->{'FriendlyName'} = $poolData->{'FriendlyName'};
+	# Set notes
+	$pool->{'Notes'} = $poolData->{'Notes'};
+
+	# Assign pool ID
+	$pool->{'ID'} = $poolIDCounter++;
+
+	# Add pool
+	$pools->{$pool->{'ID'}} = $pool;
+
+	# Link pool identifier map
+	$poolIdentifierMap->{$pool->{'InterfaceGroupID'}}->{$pool->{'Identifier'}} = $pool;
+	# Blank our pool member mapping
+	$poolMemberMap->{$pool->{'ID'}} = { };
+
+	setPoolShaperState($pool->{'ID'},SHAPER_NOTLIVE);
+
+	# Pool needs updating
+	$poolChangeQueue->{$pool->{'ID'}} = $pool;
+
+	# Resolve overrides
+	_override_resolve([$pool->{'ID'}]);
+
+	# Bump up changes
+	$stateChanged++;
+
+	return $pool->{'ID'};
 }
 
 
-# Function to get shaper state on a limit
-sub getShaperState
+# Function to remove a pool
+sub removePool
 {
-	my $lid = shift;
+	my $pid = shift;
 
 
-	if (defined($lid) && defined($limits->{$lid})) {
-		return $limits->{$lid}->{'_shaper.state'};
+	# Check pool exists first
+	if (!isPoolIDValid($pid)) {
+		return;
+	}
+
+	my $pool = $pools->{$pid};
+
+	# Check if pool is not already offlining
+	if ($pool->{'Status'} == CFGM_OFFLINE) {
+		return;
 	}
 
-	return undef;
+	my $now = time();
+
+	# Set status to offline so its caught by our garbage collector
+	$pool->{'Status'} = CFGM_OFFLINE;
+
+	# Updated pool's last updated timestamp
+	$pool->{'LastUpdate'} = $now;
+
+	# Pool needs updating
+	$poolChangeQueue->{$pool->{'ID'}} = $pool;
+
+	# Bump up changes
+	$stateChanged++;
+
+	return;
 }
 
 
-# Function to get traffic classes
-sub getTrafficClasses
+# Function to change a pool
+sub changePool
+{
+	my $poolData = shift;
+
+
+	# Check pool exists first
+	if (!isPoolIDValid($poolData->{'ID'})) {
+		$logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process pool change as there is no 'ID' attribute");
+		return;
+	}
+
+	my $pool = $pools->{$poolData->{'ID'}};
+
+	my $now = time();
+
+	my $changes = getHashChanges($pool,$poolData,[POOL_CHANGE_ATTRIBUTES]);
+	# Make changes...
+	foreach my $item (keys %{$changes}) {
+		$pool->{$item} = $changes->{$item};
+	}
+
+	# Set pool to being updated
+	$pool->{'Status'} = CFGM_CHANGED;
+	# Pool was just updated, so update our timestamp
+	$pool->{'LastUpdate'} = $now;
+
+	# Pool needs updating
+	$poolChangeQueue->{$pool->{'ID'}} = $pool;
+
+	# Bump up changes
+	$stateChanged++;
+
+	# Return what was changed
+	return dclone($changes);
+}
+
+
+# Function to return a pool
+sub getPool
+{
+	my $pid = shift;
+
+
+	if (!isPoolIDValid($pid)) {
+		return;
+	}
+
+	my $pool = dclone($pools->{$pid});
+
+	# Remove attributes?
+	delete($pool->{'.attributes'});
+	delete($pool->{'.applied_attributes'});
+
+	return $pool;
+}
+
+
+# Function to get a pool member by its identifier
+sub getPoolByIdentifer
+{
+	my ($interfaceGroupID,$identifier) = @_;
+
+
+	# Make sure both params are defined or we get warnings
+	if (!defined($interfaceGroupID) || !defined($identifier)) {
+		return;
+	}
+
+	# Maybe it doesn't exist?
+	if (!defined($poolIdentifierMap->{$interfaceGroupID}) || !defined($poolIdentifierMap->{$interfaceGroupID}->{$identifier})) {
+		return;
+	}
+
+	return dclone($poolIdentifierMap->{$interfaceGroupID}->{$identifier});
+}
+
+
+# Function to return a list of pool ID's
+sub getPools
+{
+	return (keys %{$pools});
+}
+
+
+# Function to return a pool TX interface
+sub getPoolTxInterface
+{
+	my $pid = shift;
+
+
+	# Check pool exists first
+	if (!isPoolIDValid($pid)) {
+		return;
+	}
+
+	return $config->{'interface_groups'}->{$pools->{$pid}->{'InterfaceGroupID'}}->{'txiface'};
+}
+
+
+# Function to return a pool RX interface
+sub getPoolRxInterface
+{
+	my $pid = shift;
+
+
+	# Check pool exists first
+	if (!isPoolIDValid($pid)) {
+		return;
+	}
+
+	return $config->{'interface_groups'}->{$pools->{$pid}->{'InterfaceGroupID'}}->{'rxiface'};
+}
+
+
+# Function to return a pool traffic class ID
+sub getPoolTrafficClassID
+{
+	my $pid = shift;
+
+
+	# Check pool exists first
+	if (!isPoolIDValid($pid)) {
+		return;
+	}
+
+	return $pools->{$pid}->{'ClassID'};
+}
+
+
+# Function to set pools shaper state
+sub setPoolShaperState
+{
+	my ($pid,$state) = @_;
+
+
+	# Check pool exists first
+	if (!isPoolIDValid($pid)) {
+		return;
+	}
+
+	$pools->{$pid}->{'.shaper_state'} = $state;
+
+	return $state;
+}
+
+
+# Function to get shaper state for a pool
+sub getPoolShaperState
+{
+	my $pid = shift;
+
+
+	# Check pool exists first
+	if (!isPoolIDValid($pid)) {
+		return;
+	}
+
+	return $pools->{$pid}->{'.shaper_state'};
+}
+
+
+# Function to check the pool ID exists
+sub isPoolIDValid
+{
+	my $pid = shift;
+
+
+	if (!defined($pid) || !defined($pools->{$pid})) {
+		return;
+	}
+
+	return $pid;
+}
+
+
+# Function to return if a pool is ready for any kind of modification
+sub isPoolReady
+{
+	my $pid = shift;
+
+
+	# Get state and check pool exists all in one
+	my $state;
+	if (!defined($state = getPoolShaperState($pid))) {
+		return;
+	}
+
+	return ($pools->{$pid}->{'Status'} == CFGM_ONLINE && $state == SHAPER_LIVE);
+}
+
+
+# Function to return a pool with any items changed as per overrides
+sub getEffectivePool
+{
+	my $pid = shift;
+
+
+	my $pool;
+	if (!defined($pool = getPool($pid))) {
+		return;
+	}
+
+	# If we have applied overrides, check out what changes there may be
+	if (defined(my $appliedOverrides = $pools->{$pid}->{'.applied_overrides'})) {
+		my $overrideSet;
+
+		# Loop with overrides in ascending fashion, least matches to most
+		foreach my $oid ( sort { $appliedOverrides->{$a} <=> $appliedOverrides->{$b} } keys %{$appliedOverrides}) {
+			my $override = $overrides->{$oid};
+
+			# Loop with attributes and create our override set
+			foreach my $attr (OVERRIDE_CHANGESET_ATTRIBUTES) {
+				# Set override set attribute if the override has defined it
+				if (defined($override->{$attr}) && $override->{$attr} ne "") {
+					$overrideSet->{$attr} = $override->{$attr};
+				}
+			}
+		}
+
+		# Set overrides on pool
+		if (defined($overrideSet)) {
+			foreach my $attr (keys %{$overrideSet}) {
+				$pool->{$attr} = $overrideSet->{$attr};
+			}
+		}
+	}
+
+	return $pool;
+}
+
+
+# Function to create a pool member
+sub	createPoolMember
+{
+	my $poolMemberData = shift;
+
+
+	# Check if we have all the attributes we need
+	my $isInvalid;
+	foreach my $attr (POOLMEMBER_REQUIRED_ATTRIBUTES) {
+		if (!defined($poolMemberData->{$attr})) {
+			$isInvalid = $attr;
+			last;
+		}
+	}
+	if ($isInvalid) {
+		$logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process pool member add as there is an attribute missing: '%s'",$isInvalid);
+		return;
+	}
+
+	my $poolMember;
+
+	my $now = time();
+
+	# Check if IP address is defined
+	if (!defined(isIP($poolMember->{'IPAddress'} = $poolMemberData->{'IPAddress'}))) {
+		$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Cannot process pool member add as the IPAddress is invalid");
+		return;
+	}
+	# Now check if Username its valid
+	if (!defined(isUsername($poolMember->{'Username'} = $poolMemberData->{'Username'}))) {
+		$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Cannot process pool member add as Username is invalid");
+		return;
+	}
+
+	# Check pool ID is OK
+	if (!defined($poolMember->{'PoolID'} = isPoolIDValid($poolMemberData->{'PoolID'}))) {
+		$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Cannot process pool member add for '%s' as the PoolID is invalid",
+				$poolMemberData->{'Username'}
+		);
+		return;
+	}
+
+	# Grab pool
+	my $pool = $pools->{$poolMember->{'PoolID'}};
+
+	# Check match priority ID is OK
+	if (!defined($poolMember->{'MatchPriorityID'} = isMatchPriorityIDValid($poolMemberData->{'MatchPriorityID'}))) {
+		$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Cannot process pool member add for '%s' as the MatchPriorityID is invalid",
+				$poolMemberData->{'Username'}
+		);
+		return;
+	}
+	# Check group ID is OK
+	if (!defined($poolMember->{'GroupID'} = isGroupIDValid($poolMemberData->{'GroupID'}))) {
+		$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Cannot process pool member add for '%s' as the GroupID is invalid",
+				$poolMemberData->{'Username'}
+		);
+		return;
+	}
+	# Set source
+	$poolMember->{'Source'} = $poolMemberData->{'Source'};
+	# Set when this entry was created
+	$poolMember->{'Created'} = defined($poolMemberData->{'Created'}) ? $poolMemberData->{'Created'} : $now;
+	$poolMember->{'LastUpdate'} = $now;
+	# Set when this entry expires
+	$poolMember->{'Expires'} = defined($poolMemberData->{'Expires'}) ? int($poolMemberData->{'Expires'}) : 0;
+	# Check status is OK
+	$poolMember->{'Status'} = CFGM_NEW;
+	# Set friendly name and notes
+	$poolMember->{'FriendlyName'} = $poolMemberData->{'FriendlyName'};
+	# Set notes
+	$poolMember->{'Notes'} = $poolMemberData->{'Notes'};
+
+	# Create pool member ID
+	$poolMember->{'ID'} = $poolMemberIDCounter++;
+
+	# Add pool member
+	$poolMembers->{$poolMember->{'ID'}} = $poolMember;
+
+	# Link interface IP address map
+	$interfaceIPMap->{$pool->{'InterfaceGroupID'}}->{$poolMember->{'IPAddress'}}->{$poolMember->{'ID'}} = $poolMember;
+	# Link pool map
+	$poolMemberMap->{$pool->{'ID'}}->{$poolMember->{'ID'}} = $poolMember;
+
+	# Updated pool's last updated timestamp
+	$pool->{'LastUpdate'} = $now;
+	# Make sure pool is online and not offlining
+	if ($pool->{'Status'} == CFGM_OFFLINE) {
+		$pool->{'Status'} = CFGM_ONLINE;
+	}
+
+	setPoolMemberShaperState($poolMember->{'ID'},SHAPER_NOTLIVE);
+
+	# Pool member needs updating
+	$poolMemberChangeQueue->{$poolMember->{'ID'}} = $poolMember;
+
+	# Resolve overrides, there may of been no pool members, now there is one and we may be able to apply an override
+	_override_resolve([$pool->{'ID'}]);
+
+	# Bump up changes
+	$stateChanged++;
+
+	return $poolMember->{'ID'};
+}
+
+
+# Function to remove pool member, this function is actually just going to flag it offline
+# the offline pool members will be caught by cleanup and removed, we do this because we
+# need the pool member setup in the removal functions, we cannot remove it first, and we
+# cannot allow plugins to remove internal data structures either.
+sub removePoolMember
+{
+	my $pmid = shift;
+
+
+	# Check pool member exists first
+	if (!isPoolMemberIDValid($pmid)) {
+		return;
+	}
+
+	my $poolMember = $poolMembers->{$pmid};
+
+	# Check if pool member is not already offlining
+	if ($poolMember->{'Status'} == CFGM_OFFLINE) {
+		return;
+	}
+
+	my $now = time();
+
+	# Grab pool
+	my $pool = $pools->{$poolMember->{'PoolID'}};
+
+	# Updated pool's last updated timestamp
+	$pool->{'LastUpdate'} = $now;
+
+	# Set status to offline so its caught by our garbage collector
+	$poolMember->{'Status'} = CFGM_OFFLINE;
+
+	# Update pool members last updated timestamp
+	$poolMember->{'LastUpdate'} = $now;
+
+	# Pool member needs updating
+	$poolMemberChangeQueue->{$poolMember->{'ID'}} = $poolMember;
+
+	# Bump up changes
+	$stateChanged++;
+
+	return;
+}
+
+
+# Function to change a pool member
+sub changePoolMember
+{
+	my $poolMemberData = shift;
+
+
+	# Check pool member exists first
+	if (!isPoolMemberIDValid($poolMemberData->{'ID'})) {
+		$logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process pool change as there is no 'ID' attribute");
+		return;
+	}
+
+	my $poolMember = $poolMembers->{$poolMemberData->{'ID'}};
+	my $pool = $pools->{$poolMember->{'PoolID'}};
+
+	my $now = time();
+
+	my $changes = getHashChanges($poolMember,$poolMemberData,[POOLMEMBER_CHANGE_ATTRIBUTES]);
+
+	# Make changes...
+	foreach my $item (keys %{$changes}) {
+		$poolMember->{$item} = $changes->{$item};
+	}
+
+	# Pool member isn't really updated, so we just set the last updated timestamp
+	$poolMember->{'LastUpdate'} = $now;
+	# Pool was just updated, so update our timestamp
+	$pool->{'LastUpdate'} = $now;
+
+	# Bump up changes
+	$stateChanged++;
+
+	# Return what was changed
+	return dclone($changes);
+}
+
+
+# Function to return a list of pool ID's
+sub getPoolMembers
+{
+	my $pid = shift;
+
+
+	# Check pool exists first
+	if (!isPoolIDValid($pid)) {
+		return;
+	}
+
+	# Check our member map is not undefined
+	if (!defined($poolMemberMap->{$pid})) {
+		return;
+	}
+
+	return keys %{$poolMemberMap->{$pid}};
+}
+
+
+# Function to return a pool member
+sub getPoolMember
+{
+	my $pmid = shift;
+
+
+	# Check pool member exists first
+	if (!isPoolMemberIDValid($pmid)) {
+		return;
+	}
+
+	my $poolMember = dclone($poolMembers->{$pmid});
+
+	# Remove attributes?
+	delete($poolMember->{'.attributes'});
+
+	return $poolMember;
+}
+
+
+# Function to return pool member ID's with a certain IP address
+sub getPoolMembersByIP
+{
+	my ($interfaceGroupID,$ipAddress) = @_;
+
+
+	# Make sure both params are defined or we get warnings
+	if (!defined($interfaceGroupID) || !defined($ipAddress)) {
+		return;
+	}
+
+	# Maybe it doesn't exist?
+	if (!defined($interfaceIPMap->{$interfaceGroupID}) || !defined($interfaceIPMap->{$interfaceGroupID}->{$ipAddress})) {
+		return;
+	}
+
+	return keys %{$interfaceIPMap->{$interfaceGroupID}->{$ipAddress}};
+}
+
+
+# Function to check the pool member ID exists
+sub isPoolMemberIDValid
+{
+	my $pmid = shift;
+
+
+	if (!defined($pmid) || !defined($poolMembers->{$pmid})) {
+		return;
+	}
+
+	return $pmid;
+}
+
+
+# Function to return if a pool member is ready for any kind of modification
+sub isPoolMemberReady
 {
-	my $classes = dclone($config->{'classes'});
+	my $pmid = shift;
 
 
-	# Remove default pool class if we have one
-	if (defined(my $classID = $config->{'default_pool'})) {
-		delete($classes->{$classID});
+	# Check pool exists first
+	if (!isPoolMemberIDValid($pmid)) {
+		return;
 	}
 
-	return $classes;
+	return ($poolMembers->{$pmid}->{'Status'} == CFGM_ONLINE && getPoolMemberShaperState($pmid) == SHAPER_LIVE);
 }
 
 
-# Function to get class name
-sub getTrafficClassName
+# Function to return a pool member match priority
+sub getPoolMemberMatchPriority
 {
-	my $class = shift;
+	my $pmid = shift;
 
 
-	if (defined($class) && defined($config->{'classes'}->{$class})) {
-		return $config->{'classes'}->{$class};
+	# Check pool member exists first
+	if (!isPoolMemberIDValid($pmid)) {
+		return;
 	}
 
-	return undef;
+	# NK: No actual mappping yet, we just return the ID
+	return $poolMembers->{$pmid}->{'MatchPriorityID'};
 }
 
 
-# Function to check if traffic class is valid
-sub isTrafficClassValid
+# Function to set a pool member attribute
+sub setPoolMemberAttribute
 {
-	my $class = shift;
+	my ($pmid,$attr,$value) = @_;
 
 
-	if (defined($class) && defined($config->{'classes'}->{$class})) {
-		return $class;
+	# Check pool member exists first
+	if (!isPoolMemberIDValid($pmid)) {
+		return;
 	}
 
-	return undef;
+	$poolMembers->{$pmid}->{'.attributes'}->{$attr} = $value;
+
+	return $value;
 }
 
 
-# Function to return the traffic priority based on a traffic class
-sub getTrafficPriority
+# Function to set pool member shaper state
+sub setPoolMemberShaperState
 {
-	my $class = shift;
+	my ($pmid,$state) = @_;
 
 
-	# NK: Short circuit, our ClassID = Priority
-	if (defined($class) && defined($config->{'classes'}->{$class})) {
-		return $class;
+	# Check pool member exists first
+	if (!isPoolMemberIDValid($pmid)) {
+		return;
 	}
 
-	return undef;
+	$poolMembers->{$pmid}->{'.shaper_state'} = $state;
+
+	return $state;
 }
 
 
-# Handle SIGHUP
-sub handle_SIGHUP
+# Function to get shaper state for a pool
+sub getPoolMemberShaperState
 {
-	my ($kernel, $heap, $signal_name) = @_[KERNEL, HEAP, ARG0];
+	my $pmid = shift;
 
-	$logger->log(LOG_WARN,"[CONFIGMANAGER] Got SIGHUP, ignoring for now");
-}
 
+	# Check pool member exists first
+	if (!isPoolMemberIDValid($pmid)) {
+		return;
+	}
 
+	return $poolMembers->{$pmid}->{'.shaper_state'};
+}
 
-#
-# Internal functions
-#
 
-# Function to compute the changes between two limits
-sub _getAppliedLimitChangeset
+# Function to get a pool member attribute
+sub getPoolMemberAttribute
 {
-	my ($orig,$new) = @_;
-
+	my ($pmid,$attr) = @_;
 
-	my $res;
 
-	# Loop through what can change
-	foreach my $item (LIMIT_CHANGESET_ATTRIBUTES) {
-		# 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};
-		}
+	# Check pool member exists first
+	if (!isPoolMemberIDValid($pmid)) {
+		return;
 	}
 
-	# 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};
-				   }
+	# Check if attribute exists first
+	if (!defined($poolMembers->{$pmid}->{'.attributes'}) || !defined($poolMembers->{$pmid}->{'.attributes'}->{$attr})) {
+		return;
+	}
 
-				# 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};
-					}
+	return $poolMembers->{$pmid}->{'.attributes'}->{$attr};
+}
 
-					# 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;
-		}
+# Function to remove a pool member attribute
+sub removePoolMemberAttribute
+{
+	my ($pmid,$attr) = @_;
 
 
-	# 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'});
-		}
+	# Check pool member exists first
+	if (!isPoolMemberIDValid($pmid)) {
+		return;
 	}
 
+	# Check if attribute exists first
+	if (!defined($poolMembers->{$pmid}->{'.attributes'}) || !defined($poolMembers->{$pmid}->{'.attributes'}->{$attr})) {
+		return;
+	}
 
-	return $res;
+	return delete($poolMembers->{$pmid}->{'.attributes'}->{$attr});
 }
 
 
-# This is the real function
-sub _process_limit_change
+# Create a limit, which is the combination of a pool and a pool member
+sub createLimit
 {
-	my $limit = shift;
+	my $limitData = shift;
 
 
 	# Check if we have all the attributes we need
 	my $isInvalid;
 	foreach my $attr (LIMIT_REQUIRED_ATTRIBUTES) {
-		if (!defined($limit->{$attr})) {
+		if (!defined($limitData->{$attr})) {
 			$isInvalid = $attr;
 			last;
 		}
 	}
 	if ($isInvalid) {
-		$logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process limit change as thre is an attribute missing: '$isInvalid'");
+		$logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process limit add as there is an attribute missing: '%s'",$isInvalid);
 		return;
 	}
 
-	# We start off blank so we only pull in whats supported
-	my $limitChange;
-	if (!defined($limitChange->{'Username'} = $limit->{'Username'})) {
-		$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Cannot process limit change as username is invalid.");
-		return;
-	}
-	$limitChange->{'Username'} = $limit->{'Username'};
-	$limitChange->{'IP'} = $limit->{'IP'};
-	# Check group is OK
-	if (!defined($limitChange->{'GroupID'} = checkGroupID($limit->{'GroupID'}))) {
-		$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Cannot process limit change for '%s' as the GroupID is invalid.",$limit->{'Username'});
-		return;
-	}
-	# Check interface group ID is OK
-	if (!defined($limitChange->{'InterfaceGroupID'} = checkInterfaceGroupID($limit->{'InterfaceGroupID'}))) {
-		$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Cannot process limit change for '%s' as the InterfaceGroupID is invalid.",$limit->{'Username'});
-		return;
-	}
-	# Check match priority is OK
-	if (!defined($limitChange->{'MatchPriorityID'} = checkMatchPriorityID($limit->{'MatchPriorityID'}))) {
-		$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Cannot process limit change for '%s' as the MatchPriorityID is invalid.",$limit->{'Username'});
-		return;
-	}
-	# Check class is OK
-	if (!defined($limitChange->{'ClassID'} = checkClassID($limit->{'ClassID'}))) {
-		$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Cannot process limit change for '%s' as the ClassID is invalid.",$limit->{'Username'});
-		return;
-	}
-	# Make sure things are not attached to the default pool
-	if (defined($config->{'default_pool'}) && $limitChange->{'ClassID'} eq $config->{'default_pool'}) {
-		$logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process limit for '%s' as the ClassID is the 'default_pool' ClassID.",$limit->{'Username'});
+	# Check if IP address is defined
+	if (!defined(isIP($limitData->{'IPAddress'} = $limitData->{'IPAddress'}))) {
+		$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Cannot process limit add as the IP address is invalid");
 		return;
 	}
 
-	$limitChange->{'TrafficLimitTx'} = $limit->{'TrafficLimitTx'};
-	$limitChange->{'TrafficLimitRx'} = $limit->{'TrafficLimitRx'};
-	# Take base limits if we don't have any burst values set
-	$limitChange->{'TrafficLimitTxBurst'} = $limit->{'TrafficLimitTxBurst'};
-	$limitChange->{'TrafficLimitRxBurst'} = $limit->{'TrafficLimitRxBurst'};
-
-	# If we don't have burst limits, set them to the traffic limit, and reset the limit to 25%
-	if (!defined($limitChange->{'TrafficLimitTxBurst'})) {
-		$limitChange->{'TrafficLimitTxBurst'} = $limitChange->{'TrafficLimitTx'};
-		$limitChange->{'TrafficLimitTx'} = int($limitChange->{'TrafficLimitTxBurst'}/4);
-	}
-	if (!defined($limitChange->{'TrafficLimitRxBurst'})) {
-		$limitChange->{'TrafficLimitRxBurst'} = $limitChange->{'TrafficLimitRx'};
-		$limitChange->{'TrafficLimitRx'} = int($limitChange->{'TrafficLimitRxBurst'}/4);
-	}
-
-	# Set when this entry expires
-	$limitChange->{'Expires'} = defined($limit->{'Expires'}) ? int($limit->{'Expires'}) : 0;
-
-	# Set friendly name and notes
-	$limitChange->{'FriendlyName'} = $limit->{'FriendlyName'};
-	$limitChange->{'Notes'} = $limit->{'Notes'};
-
-	# Set when this entry was created
-	$limitChange->{'Created'} = defined($limit->{'Created'}) ? $limit->{'Created'} : time();
-
-	# Check status is OK
-	if (!($limitChange->{'Status'} = checkStatus($limit->{'Status'}))) {
-		$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Cannot process user change for '%s' as the Status is invalid.",$limit->{'Username'});
+	my $poolIdentifier = $limitData->{'Username'};
+	my $poolData = {
+		'FriendlyName' => $limitData->{'IPAddress'},
+		'Identifier' => $poolIdentifier,
+		'InterfaceGroupID' => $limitData->{'InterfaceGroupID'},
+		'ClassID' => $limitData->{'ClassID'},
+		'TrafficLimitTx' => $limitData->{'TrafficLimitTx'},
+		'TrafficLimitTxBurst' => $limitData->{'TrafficLimitTxBurst'},
+		'TrafficLimitRx' => $limitData->{'TrafficLimitRx'},
+		'TrafficLimitRxBurst' => $limitData->{'TrafficLimitRxBurst'},
+		'Expires' => $limitData->{'Expires'},
+		'Notes' => $limitData->{'Notes'},
+		'Status' => $limitData->{'Status'},
+		'Source' => $limitData->{'Source'}
+	};
+
+	# If we didn't succeed just exit
+	my $poolID;
+	if (!defined($poolID = createPool($poolData))) {
 		return;
 	}
 
-	$limitChange->{'Source'} = $limit->{'Source'};
-
-	# Create a unique limit identifier
-	my $limitID = sprintf('%s/%s',$limit->{'Username'},$limit->{'IP'});
-	# If we've not seen it
-	my $lid;
-	if (!defined($lid = $limitIDMap->{$limitID})) {
-		# Give it the next limit ID in the list
-		$limitIDMap->{$limitID} = $lid = ++$limitIDCounter;
+	my $poolMemberData = {
+		'FriendlyName' => $limitData->{'FriendlyName'},
+		'Username' => $limitData->{'Username'},
+		'IPAddress' => $limitData->{'IPAddress'},
+		'InterfaceGroupID' => $limitData->{'InterfaceGroupID'},
+		'MatchPriorityID' => $limitData->{'MatchPriorityID'},
+		'PoolID' => $poolID,
+		'GroupID' => $limitData->{'GroupID'},
+		'Expires' => $limitData->{'Expires'},
+		'Notes' => $limitData->{'Notes'},
+		'Status' => $limitData->{'Status'},
+		'Source' => $limitData->{'Source'}
+	};
+
+	my $poolMemberID;
+	if (!defined($poolMemberID = createPoolMember($poolMemberData))) {
+		return;
 	}
 
-	# Set the user ID before we post to the change queue
-	$limitChange->{'ID'} = $lid;
-	$limitChange->{'LastUpdate'} = time();
-	# Push change to change queue
-	$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->{'InterfaceGroupID'}}->{$limit->{'IP'}}->{$lid});
-		# Check if we can delete the IP too
-		if (keys %{$limitIPMap->{$limit->{'InterfaceGroupID'}}->{$limit->{'IP'}}} == 0) {
-			delete($limitIPMap->{$limit->{'InterfaceGroupID'}}->{$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);
-	}
+	return ($poolMemberID,$poolID);
 }
 
 
-# This is the real process_override_change function
-sub _process_override_change
+# Function to create a override
+sub createOverride
 {
-	my $override = shift;
+	my $overrideData = shift;
 
 
-	# Pull in mandatory items and check if the result is valid
-	my $overrideChange;
+	# Check that we have at least one match attribute
 	my $isValid = 0;
 	foreach my $item (OVERRIDE_MATCH_ATTRIBUTES) {
-		$overrideChange->{$item} = $override->{$item};
 		$isValid++;
 	}
-	# Make sure we have at least 1
 	if (!$isValid) {
 		$logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process override as there is no selection attribute");
 		return;
 	}
 
-	# Pull in attributes that can be changed
-	foreach my $item (OVERRIDE_CHANGESET_ATTRIBUTES) {
-		$overrideChange->{$item} = $override->{$item};
+	my $override;
+
+	my $now = time();
+
+	# Pull in attributes
+	foreach my $item (OVERRIDE_ATTRIBUTES) {
+		$override->{$item} = $overrideData->{$item};
 	}
 
 	# Check group is OK
-	if (defined($overrideChange->{'GroupID'}) && !checkGroupID($overrideChange->{'GroupID'})) {
-		$logger->log(LOG_DEBUG,'[CONFIGMANAGER] Cannot process override for "User: %s, IP: %s, GroupID: %s" as the GroupID is invalid.',
-			prettyUndef($overrideChange->{'Username'}),prettyUndef($overrideChange->{'IP'}),prettyUndef($overrideChange->{'GroupID'})
+	if (defined($override->{'GroupID'}) && !isGroupIDValid($override->{'GroupID'})) {
+		$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Cannot process override for user '%s', IP '%s', GroupID '%s' as the GroupID is ".
+				"invalid",
+				prettyUndef($override->{'Username'}),
+				prettyUndef($override->{'IPAddress'}),
+				prettyUndef($override->{'GroupID'})
 		);
 		return;
 	}
 
 	# Check class is OK
-	if (defined($overrideChange->{'ClassID'}) && !checkClassID($overrideChange->{'ClassID'})) {
-		$logger->log(LOG_DEBUG,'[CONFIGMANAGER] Cannot process override for "User: %s, IP: %s, GroupID: %s" as the ClassID is invalid.',
-			prettyUndef($overrideChange->{'Username'}),prettyUndef($overrideChange->{'IP'}),prettyUndef($overrideChange->{'GroupID'})
+	if (defined($override->{'ClassID'}) && !isTrafficClassIDValid($override->{'ClassID'})) {
+		$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Cannot process override for user '%s', IP '%s', GroupID '%s' as the ClassID is ".
+				"invalid",
+				prettyUndef($override->{'Username'}),
+				prettyUndef($override->{'IPAddress'}),
+				prettyUndef($override->{'GroupID'})
 		);
 		return;
 	}
 
+	# Set source
+	$override->{'Source'} = $overrideData->{'Source'};
+	# Set when this entry was created
+	$override->{'Created'} = defined($overrideData->{'Created'}) ? $overrideData->{'Created'} : $now;
+	$override->{'LastUpdate'} = $now;
 	# Set when this entry expires
-	$overrideChange->{'Expires'} = defined($override->{'Expires'}) ? int($override->{'Expires'}) : 0;
-
+	$override->{'Expires'} = defined($overrideData->{'Expires'}) ? int($overrideData->{'Expires'}) : 0;
+	# Check status is OK
+	$override->{'Status'} = CFGM_NEW;
 	# Set friendly name and notes
-	$overrideChange->{'FriendlyName'} = $override->{'FriendlyName'};
-	$overrideChange->{'Notes'} = $override->{'Notes'};
-
-	# Set when this entry was created
-	$overrideChange->{'Created'} = defined($override->{'Created'}) ? $override->{'Created'} : time();
-
-	$overrideChange->{'LastUpdate'} = time();
+	$override->{'FriendlyName'} = $overrideData->{'FriendlyName'};
+	# Set notes
+	$override->{'Notes'} = $overrideData->{'Notes'};
 
-	# 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;
-}
+	# Create pool member ID
+	$override->{'ID'} = $overrideIDCounter++;
 
+	# Add override
+	$overrides->{$override->{'ID'}} = $override;
 
-# Process actual override removal
-sub _process_override_remove
-{
-	my $override = shift;
-	my $oid = $override->{'ID'};
+	# Resolve overrides
+	_override_resolve(undef,[$override->{'ID'}]);
 
+	# Bump up changes
+	$stateChanged++;
 
-	# 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'}});
-	}
+	return $override->{'ID'};
 }
 
 
-# Do the actual limit queue processing
-sub _process_limit_change_queue
+# Function to remove an override
+sub removeOverride
 {
-	my $kernel = shift;
-
-
-	# Now
-	my $now = time();
-
-	# Loop with changes in queue
-	foreach my $lid (keys %{$limitChangeQueue}) {
-		# Changes for limit
-		# Minimum required info is:
-		# - Username
-		# - IP
-		# - Status
-		# - LastUpdate
-		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?");
-				$updateShaper = 1;
-
-				# Remove from change queue
-				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");
-				$updateShaper = 1;
-
-				# Remove from change queue
-				delete($limitChangeQueue->{$lid});
-
-			# Offline notification, this we going to treat specially
-			} elsif ($climit->{'Status'} eq "offline") {
-
-				# We first check if this update was received some time ago, and if it exceeds our expire time
-				# We don't want to immediately remove a limit, only for him to come back on a few seconds later, the cost in exec()'s
-				# would be pretty high
-				if ($now - $climit->{'LastUpdate'} > TIMEOUT_EXPIRE_OFFLINE) {
-
-					# Remove entry if no longer live
-					if ($glimit->{'_shaper.state'} == SHAPER_NOTLIVE) {
-						$logger->log(LOG_INFO,"[CONFIGMANAGER] Limit '$climit->{'Username'}' [$lid] offline and removed from shaper");
-
-						_process_limit_remove($kernel,$glimit);
-
-						# Next record, we don't want to do any updates below
-						next;
+	my $oid = shift;
 
-					# Push to shaper
-					} elsif ($glimit->{'_shaper.state'} == SHAPER_LIVE) {
-						$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Limit '$climit->{'Username'}' [$lid] offline, queue remove from shaper");
 
-						_process_limit_remove($kernel,$glimit);
+	# Check override exists first
+	if (!isOverrideIDValid($oid)) {
+		return;
+	}
 
-					} else {
-						$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Limit '$climit->{'Username'}' [$lid], limit in list, but offline now and".
-								" expired, still live, waiting for shaper");
-					}
-				}
-			}
+	my $override = $overrides->{$oid};
 
-			# Update the limit data
-			$glimit->{'Status'} = $climit->{'Status'};
-			$glimit->{'LastUpdate'} = $climit->{'LastUpdate'};
+	# Remove override from pools that have it and trigger a change
+	if (defined($override->{'.applied_pools'})) {
+		foreach my $pid (keys %{$override->{'.applied_pools'}}) {
+			my $pool = $pools->{$pid};
 
-			# Set these if they exist
-			if (defined($climit->{'Expires'})) {
-				$glimit->{'Expires'} = $climit->{'Expires'};
-			}
-			if (defined($climit->{'FriendlyName'})) {
-				$glimit->{'FriendlyName'} = $climit->{'FriendlyName'};
-			}
-			if (defined($climit->{'Notes'})) {
-				$glimit->{'Notes'} = $climit->{'Notes'};
-			}
+			# Remove overrides from the pool
+			delete($pool->{'.applied_overrides'}->{$override->{'ID'}});
 
-			# 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);
-				}
+			# If the pool is online and live, trigger a change
+			if ($pool->{'Status'} == CFGM_ONLINE && getPoolShaperState($pid) == SHAPER_LIVE) {
+				$poolChangeQueue->{$pool->{'ID'}} = $pool;
+				$pool->{'Status'} = CFGM_CHANGED;
 			}
+		}
+	}
 
+	# Remove override
+	delete($overrides->{$override->{'ID'}});
 
-		# 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;
+	# Bump up changes
+	$stateChanged++;
 
-				# Initialize the IP map if the interface group ID hash is undefined
-				if (!defined($limitIPMap->{$climit->{'InterfaceGroupID'}})) {
-					$limitIPMap->{$climit->{'InterfaceGroupID'}} = { };
-				}
+	return;
+}
 
-				# We first going to look for IP conflicts...
-				my @ipLimits = keys %{$limitIPMap->{$climit->{'InterfaceGroupID'}}->{$climit->{'IP'}}};
-				if (
-					# If there is already an entry and its not us ...
-					( @ipLimits == 1 && !defined($limitIPMap->{$climit->{'InterfaceGroupID'}}->{$climit->{'IP'}}->{$lid}) )
-					# Or if there is more than 1 entry...
-					|| @ipLimits > 1
-				) {
-					# We not going to post this to the shaper, but we are going to override the status
-					$climit->{'Status'} = 'conflict';
-					$climit->{'_shaper.state'} = SHAPER_NOTLIVE;
-					# Give a bit of info
-					my @conflictUsernames;
-					foreach my $lid (@ipLimits) {
-						push(@conflictUsernames,$limits->{$lid}->{'Username'});
-					}
-					# Output log line
-					$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
-					# IP from the shaper below...
-					foreach my $lid2 (@ipLimits) {
-						# Check if the limit has been setup already (all but the limit we busy with, as its setup below)
-						if (defined($limitIPMap->{$climit->{'InterfaceGroupID'}}->{$climit->{'IP'}}->{$lid2})) {
-							my $glimit2 = $limits->{$lid2};
-
-							# 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, 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';
-							}
-						}
-					}
 
-				# All looks good, no conflicts, we're set to add this limit!
-				} else {
-					# Post to the limit to the shaper
-					$climit->{'_shaper.state'} = SHAPER_PENDING;
-					$updateShaper = 1;
-				}
+# Function to change an override
+sub changeOverride
+{
+	my $overrideData = shift;
 
-				# Set this UID as using this IP
-				$limitIPMap->{$climit->{'InterfaceGroupID'}}->{$climit->{'IP'}}->{$lid} = 1;
 
-				# This is now live
-				$limits->{$lid} = $climit;
+	# Check override exists first
+	if (!isOverrideIDValid($overrideData->{'ID'})) {
+		$logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process override change as there is no 'ID' attribute");
+		return;
+	}
 
-				# Resolve this limit's overrides, this works on the GLOBAL $limits!!
-				_resolve_overrides($lid);
+	my $override = $overrides->{$overrideData->{'ID'}};
 
-				# 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);
-				}
+	my $now = time();
 
-			# 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".
-						" global list");
-			}
+	my $changes = getHashChanges($override,$overrideData,[OVERRIDE_CHANGE_ATTRIBUTES]);
+	# Make changes...
+	foreach my $item (keys %{$changes}) {
+		$override->{$item} = $changes->{$item};
+	}
 
-			# Remove from change queue
-			delete($limitChangeQueue->{$lid});
-		}
+	# Set status to updated
+	$override->{'Status'} = CFGM_CHANGED;
+	# Set timestamp
+	$override->{'LastUpdate'} = $now;
 
-		# Well, we changed something...
-		$stateChanged++;
+	# Resolve overrides to see if any attributes changed, we only do this if it already matches
+	# We do NOT support changing match attributes
+	if (defined($override->{'.applied_pools'}) && (my @pids = keys %{$override->{'.applied_pools'}}) > 0) {
+		_override_resolve([@pids],[$override->{'ID'}]);
 	}
 
+	# Bump up changes
+	$stateChanged++;
 
+	# Return what was changed
+	return dclone($changes);
 }
 
 
-# Process cleanup of limits
-sub _process_limit_cleanup
+# Function to check the override ID exists
+sub isOverrideIDValid
 {
-	my $kernel = shift;
-
+	my $oid = shift;
 
-	my $now = time();
 
-	# Loop with limits and check for expired entries
-	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");
-			# Looks like this limit has expired?
-			my $climit = {
-				'Username' => $glimit->{'Username'},
-				'IP' => $glimit->{'IP'},
-				'Status' => 'offline',
-				'LastUpdate' => $glimit->{'LastUpdate'},
-			};
-			# Add to change queue
-			$limitChangeQueue->{$lid} = $climit;
-		}
+	if (!defined($oid) || !defined($overrides->{$oid})) {
+		return;
 	}
+
+	return $oid;
 }
 
 
-# Do the actual override queue processing
-# This function returns 1 if it made a change, 0 if not
-sub _process_override_change_queue
+# Function to get traffic classes
+sub getTrafficClasses
 {
-	my $kernel = shift;
+	my $classes = dclone($config->{'classes'});
 
 
-	# Now
-	my $now = time();
+	# Remove default pool class if we have one
+	if (defined(my $classID = $config->{'default_pool'})) {
+		delete($classes->{$classID});
+	}
 
-	# Overrides changed
-	my $overridesChanged = 0;
+	return $classes;
+}
 
-	# 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} = '';
-			}
-		}
+# Function to get all traffic classes
+sub getAllTrafficClasses
+{
+	my $classes = dclone($config->{'classes'});
 
-		# This is now live
-		$overrides->{$oid} = $coverride;
-		$overrideMap->{$coverride->{'GroupID'}}->{$coverride->{'Username'}}->{$coverride->{'IP'}} = $coverride;
+	return $classes;
+}
 
-		# Remove from change queue
-		delete($overrideChangeQueue->{$oid});
 
-		$overridesChanged = 1;
+# Function to get class name
+sub getTrafficClassName
+{
+	my $classID = shift;
+
 
-		# Something changed...
-		$stateChanged++;
+	if (!isTrafficClassIDValid($classID)) {
+		return;
 	}
 
-	return $overridesChanged;
+	return $config->{'classes'}->{$classID};
 }
 
 
-# Process cleanup of overrides
-# This function returns 1 if it made a change, 0 if not
-sub _process_override_cleanup
+# Function to check if traffic class is valid
+sub isTrafficClassIDValid
 {
-	my $overridesChanged = 0;
+	my $classID = shift;
 
 
-	my $now = time();
-
-	# 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 (!defined($classID) || !defined($config->{'classes'}->{$classID})) {
+		return;
 	}
 
-	return $overridesChanged;
+	return $classID;
 }
 
 
-
-# This function calls _resolve_overrides() plus posts events to the shaper
-sub _resolve_overrides_and_post
+# Function to return the traffic priority based on a traffic class
+sub getTrafficClassPriority
 {
-	my ($kernel,$lid) = @_;
+	my $classID = shift;
 
 
-	# Check if any items actually were changed
-	my @changed;
-	if (!(@changed = _resolve_overrides())) {
+	# Check it exists first
+	if (!isTrafficClassIDValid($classID)) {
 		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);
-		}
-	}
+	# NK: Short circuit, our ClassID = Priority
+	return $classID;
 }
 
 
-# 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
+#
+# Internal functions
+#
+
+
+# Resolve all overrides or those linked to a pid or oid
+# We take 2 optional argument, which is a single override and a single pool to process
+sub _override_resolve
 {
-	my $lid = shift;
+	my ($pids,$oids) = @_;
 
 
-	# Hack to intercept and create a single element hash
-	my $limitHash;
-	if (defined($lid)) {
-		$limitHash->{$lid} = $limits->{$lid};
+	# Hack to intercept and create a single element hash if we get ID's above
+	my $poolHash;
+	if (defined($pids)) {
+		foreach my $pid (@{$pids}) {
+			$poolHash->{$pid} = $pools->{$pid};
+		}
+	} else {
+		$poolHash = $pools;
+	}
+	my $overrideHash;
+	if (defined($oids)) {
+		foreach my $oid (@{$oids}) {
+			$overrideHash->{$oid} = $overrides->{$oid};
+		}
 	} else {
-		$limitHash = $limits;
+		$overrideHash = $overrides;
 	}
 
-	# Loop with all limits, keep a list of lid's updated
-	my @overridden;
-	while ((my $lid, my $limit) = each(%{$limitHash})) {
-		my $overrideResult;
+	# Loop with all pools, keep a list of pid's updated
+	my $matchList;
+	while ((my $pid, my $pool) = each(%{$poolHash})) {
+		# Build a candidate from the pool
+		my $candidate = {
+			'PoolIdentifier' => $pool->{'Identifier'},
+		};
 
-		# Loop with the attributes in matching order
-		foreach my $attrSet (OVERRIDE_MATCH_CRITERIA) {
-			# Start with a blank match
-			my $criteria = { 'GroupID' => '', 'Username' => '', 'IP' => '' };
+		# If we only have 1 member in the pool, add its username, IP and group
+		if ((my ($pmid) = getPoolMembers($pid)) == 1) {
+			my $poolMember = getPoolMember($pmid);
+			$candidate->{'Username'} = $poolMember->{'Username'};
+			$candidate->{'IPAddress'} = $poolMember->{'IPAddress'};
+			$candidate->{'GroupID'} = $poolMember->{'GroupID'};
+		}
+		# Loop with all overrides and generate a match list
+		while ((my $oid, my $override) = each(%{$overrideHash})) {
 
-			# Build match from user
-			foreach my $attr (@{$attrSet}) {
-				if ($attr ne '') {
-					$criteria->{$attr} = $limit->{$attr};
-				}
-			}
+			my $numMatches = 0;
+			my $numMismatches = 0;
+
+			# Loop with the attributes and check for a full match
+			foreach my $attr (OVERRIDE_MATCH_ATTRIBUTES) {
 
-			# 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 this attribute in the override is set, then lets check it
+				if (defined($override->{$attr}) && $override->{$attr} ne "") {
+					# Check for match or mismatch
+					if (defined($candidate->{$attr}) && $candidate->{$attr} eq $override->{$attr}) {
+						$numMatches++;
+					} else {
+						$numMismatches++;
 					}
 				}
 			}
+
+			# Setup the match list with what was matched
+			if ($numMatches && !$numMismatches) {
+				$matchList->{$pid}->{$oid} = $numMatches;
+			} else {
+				$matchList->{$pid}->{$oid} = undef;
+			}
 		}
+	}
+
+	# Loop with the match list
+	foreach my $pid (keys %{$matchList}) {
+		my $pool = $pools->{$pid};
+		# Original Effective pool
+		my $oePool = getEffectivePool($pid);
 
-		# 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);
+		# Loop with overrides for this pool
+		foreach my $oid (keys %{$matchList->{$pid}}) {
+			my $override = $overrides->{$oid};
+
+			# If we have a match, record it in pools & overrides
+			if (defined($matchList->{$pid}->{$oid})) {
+
+				# Setup trakcing of what is applied to what
+				$overrides->{$oid}->{'.applied_pools'}->{$pid} = $matchList->{$pid}->{$oid};
+				$pools->{$pid}->{'.applied_overrides'}->{$oid} = $matchList->{$pid}->{$oid};
+
+				$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Override '%s' [%s] applied to pool '%s' [%s]",
+						$override->{'FriendlyName'},
+						$override->{'ID'},
+						$pool->{'Identifier'},
+						$pool->{'ID'}
+				);
+
+			# We didn't match, but we may of matched before?
+			} else {
+				# There was an override before, so something changed now that there is none
+				if (defined($pools->{$pid}->{'.applied_overrides'}->{$oid})) {
+					# Remove overrides
+					delete($pools->{$pid}->{'.applied_overrides'}->{$oid});
+					delete($overrides->{$oid}->{'.applied_pools'}->{$pid});
+
+					$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Override '%s' no longer applies to pool '%s' [%s]",
+							$override->{'ID'},
+							$pool->{'Identifier'},
+							$pool->{'ID'}
+					);
+				}
+			}
+		}
+		# New Effective pool
+		my $nePool = getEffectivePool($pid);
+
+		# Get changes between effective pool states
+		my $poolChanges = getHashChanges($oePool,$nePool,[OVERRIDE_CHANGESET_ATTRIBUTES]);
+
+		# If there were pool changes, trigger a pool update
+		if (keys %{$poolChanges} > 0) {
+			# If the pool is currently online and live, trigger a change
+			if ($pool->{'Status'} == CFGM_ONLINE && getPoolShaperState($pid) == SHAPER_LIVE) {
+				$pool->{'Status'} = CFGM_CHANGED;
+				$poolChangeQueue->{$pool->{'ID'}} = $pool;
+			}
 		}
+	}
+}
+
 
-		$limit->{'override'} = $overrideResult;
+# Remove pool override information
+sub _override_remove_pool
+{
+	my $pid = shift;
+
+
+	if (!isPoolIDValid($pid)) {
+		return;
 	}
 
-	return @overridden;
+	my $pool = $pools->{$pid};
+
+	# Remove pool from overrides if there are any
+	if (defined($pool->{'.applied_overrides'})) {
+		foreach my $oid (keys %{$pool->{'.applied_overrides'}}) {
+			delete($overrides->{$oid}->{'.applied_pools'}->{$pool->{'ID'}});
+		}
+	}
 }
 
 
@@ -1909,13 +2951,20 @@ sub _load_statefile
 	# Pull in a hash for our statefile
 	my %stateHash;
 	if (! tie %stateHash, 'Config::IniFiles', ( -file => $config->{'statefile'} )) {
-		my $reason = $1 || "Config file blank?";
-		$logger->log(LOG_ERR,"[CONFIGMANAGER] Failed to open statefile '".$config->{'statefile'}."': $reason");
-		# NK: Breaks load on blank file
+		# Check if we got errors, if we did use them for our reason
+		my @errors = @Config::IniFiles::errors;
+		my $reason = $1 || join('; ',@errors) || "Config file blank?";
+
+		$logger->log(LOG_ERR,"[CONFIGMANAGER] Failed to open statefile '%s': %s",$config->{'statefile'},$reason);
+
 		# Set it to undef so we don't overwrite it...
-		#$config->{'statefile'} = undef;
+		if (@errors) {
+			$config->{'statefile'} = undef;
+		}
+
 		return;
 	}
+
 	# Grab the object handle
 	my $state = tied( %stateHash );
 
@@ -1923,40 +2972,60 @@ sub _load_statefile
 	foreach my $section ($state->GroupMembers('override')) {
 		my $override = $stateHash{$section};
 
-		# Our user override
+		# Loop with the persistent attributes and create our hash
 		my $coverride;
 		foreach my $attr (OVERRIDE_PERSISTENT_ATTRIBUTES) {
 			if (defined($override->{$attr})) {
+				# If its an array, join all the items
+				if (ref($override->{$attr}) eq "ARRAY") {
+					$override->{$attr} = join("\n",@{$override->{$attr}});
+				}
 				$coverride->{$attr} = $override->{$attr};
 			}
 		}
 
 		# Proces this override
-		_process_override_change($coverride);
+		createOverride($coverride);
 	}
 
-	# Loop with persistent limits
-	foreach my $section ($state->GroupMembers('persist')) {
-		my $user = $stateHash{$section};
+	# Loop with pools
+	foreach my $section ($state->GroupMembers('pool')) {
+		my $pool = $stateHash{$section};
 
-		# User to push through to process change
-		my $cuser;
-		foreach my $attr (LIMIT_PERSISTENT_ATTRIBUTES) {
-			if (defined($user->{$attr})) {
-				$cuser->{$attr} = $user->{$attr};
+		# Loop with the attributes to create the hash
+		my $cpool;
+		foreach my $attr (POOL_PERSISTENT_ATTRIBUTES) {
+			if (defined($pool->{$attr})) {
+				# If its an array, join all the items
+				if (ref($pool->{$attr}) eq "ARRAY") {
+					$pool->{$attr} = join("\n",@{$pool->{$attr}});
+				}
+				$cpool->{$attr} = $pool->{$attr};
 			}
 		}
-		# This is a new entry
-		$cuser->{'Status'} = 'new';
 
-		# Check username & IP are defined
-		if (!defined($cuser->{'Username'}) || !defined($cuser->{'IP'})) {
-			$logger->log(LOG_WARN,"[CONFIGMANAGER] Failed to load persistent user with no username or IP '$section'");
-			next;
+		# Process this pool
+		createPool($cpool);
+	}
+
+	# Loop with pool members
+	foreach my $section ($state->GroupMembers('pool_member')) {
+		my $poolMember = $stateHash{$section};
+
+		# Loop with the attributes to create the hash
+		my $cpoolMember;
+		foreach my $attr (POOLMEMBER_PERSISTENT_ATTRIBUTES) {
+			if (defined($poolMember->{$attr})) {
+				# If its an array, join all the items
+				if (ref($poolMember->{$attr}) eq "ARRAY") {
+					$poolMember->{$attr} = join("\n",@{$poolMember->{$attr}});
+				}
+				$cpoolMember->{$attr} = $poolMember->{$attr};
+			}
 		}
 
-		# Process this user
-		_process_limit_change($cuser);
+		# Process this pool member
+		createPoolMember($cpoolMember);
 	}
 }
 
@@ -1978,8 +3047,8 @@ sub _write_statefile
 	}
 
 	# 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");
+	if (!(keys %{$pools}) && !(keys %{$overrides})) {
+		$logger->log(LOG_WARN,"[CONFIGMANAGER] Not writing state file as there are no active pools or overrides");
 		return;
 	}
 
@@ -1990,46 +3059,65 @@ sub _write_statefile
 	# Create new state file object
 	my $state = new Config::IniFiles();
 
-	# Loop with persistent limits, these are limits with expires = 0
-	while ((undef, my $limit) = each(%{$limits})) {
-		# Skip over dynamic entries, we only want persistent ones unless we doing a full write
-		next if (!$fullWrite && $limit->{'Source'} eq "plugin.radius");
-
-		# Pull in the section name
-		my $section = "persist " . $limit->{'Username'};
+	# Loop with overrides
+	while ((my $oid, my $override) = each(%{$overrides})) {
+		# Create a section name
+		my $section = "override " . $oid;
 
-		# Add a new section for this user
+		# Add a section for this override
 		$state->AddSection($section);
-		# Items we want for persistent entries
-		foreach my $pItem (LIMIT_PERSISTENT_ATTRIBUTES) {
+		# Attributes we want to save for this override
+		foreach my $attr (OVERRIDE_PERSISTENT_ATTRIBUTES) {
 			# Set items up
-			if (defined(my $value = $limit->{$pItem})) {
-				$state->newval($section,$pItem,$value);
+			if (defined(my $value = $overrides->{$oid}->{$attr})) {
+				$state->newval($section,$attr,$value);
 			}
 		}
+
 	}
 
-	# Loop with overrides
-	foreach my $oid (keys %{$overrides}) {
-		# Pull in the section name
-		my $section = "override " . $oid;
+	# Loop with pools
+	while ((my $pid, my $pool) = each(%{$pools})) {
+		# Skip over dynamic entries, we only want persistent ones unless we doing a full write
+		next if (!$fullWrite && $pool->{'Source'} eq "plugin.radius");
+
+		# Create a section name
+		my $section = "pool " . $pid;
 
-		# Add a new section for this user
+		# Add a section for this pool
 		$state->AddSection($section);
-		# Items we want for override entries
-		foreach my $pItem (OVERRIDE_PERSISTENT_ATTRIBUTES) {
+		# Persistent attributes we want
+		foreach my $attr (POOL_PERSISTENT_ATTRIBUTES) {
 			# Set items up
-			if (defined(my $value = $overrides->{$oid}->{$pItem})) {
-				$state->newval($section,$pItem,$value);
+			if (defined(my $value = $pool->{$attr})) {
+				$state->newval($section,$attr,$value);
 			}
 		}
 
+		# Save pool members too
+		foreach my $pmid (keys %{$poolMemberMap->{$pid}}) {
+			# Create a section name for the pool member
+			$section = "pool_member " . $pmid;
+
+			# Add a new section for this pool member
+			$state->AddSection($section);
+
+			my $poolMember = $poolMembers->{$pmid};
+
+			# Items we want for persistent entries
+			foreach my $attr (POOLMEMBER_PERSISTENT_ATTRIBUTES) {
+				# Set items up
+				if (defined(my $value = $poolMember->{$attr})) {
+					$state->newval($section,$attr,$value);
+				}
+			}
+		}
 	}
 
 	# Check for an error
 	my $newFilename = $config->{'statefile'}.".new";
 	if (!defined($state->WriteConfig($newFilename))) {
-		$logger->log(LOG_ERR,"[CONFIGMANAGER] Failed to write temporary statefile '$newFilename': $!");
+		$logger->log(LOG_ERR,"[CONFIGMANAGER] Failed to write temporary statefile '%s': %s",$newFilename,$!);
 		return;
 	}
 
@@ -2038,13 +3126,13 @@ sub _write_statefile
 	if (-f $config->{'statefile'}) {
 		# Check if we could rename/move
 		if (!rename($config->{'statefile'},$bakFilename)) {
-			$logger->log(LOG_ERR,"[CONFIGMANAGER] Failed to rename '%s' to '$bakFilename': $!",$config->{'statefile'});
+			$logger->log(LOG_ERR,"[CONFIGMANAGER] Failed to rename '%s' to '%s': %s",$config->{'statefile'},$bakFilename,$!);
 			return;
 		}
 	}
 	# Move the new filename in place
 	if (!rename($newFilename,$config->{'statefile'})) {
-		$logger->log(LOG_ERR,"[CONFIGMANAGER] Failed to rename '$newFilename' to '%s': $!",$config->{'statefile'});
+		$logger->log(LOG_ERR,"[CONFIGMANAGER] Failed to rename '%s' to '%s': %s",$newFilename,$config->{'statefile'},$!);
 		return;
 	}
 
-- 
GitLab