Skip to content
Snippets Groups Projects
Commit 332aed88 authored by Olav Morken's avatar Olav Morken
Browse files

Changed MemcacheStore to use the Memcache class.

git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@558 44740490-163a-0410-bde0-09ae8108e29a
parent a80cca35
No related branches found
No related tags found
No related merge requests found
<?php
/* We need access to the configuration from config/config.php. */
require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Configuration.php');
/* For access to SimpleSAML_Utilities::transposeArray. */
require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Utilities.php');
/* For the interface that objects can export to allow us to see if it
* is modified or not.
*/
require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/ModifiedInfo.php');
require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Memcache.php');
/*
* This file is part of SimpleSAMLphp. See the file COPYING in the
* root of the distribution for licence information.
*
* This file implements a storage class which stores data to one or more
* groups memcache servers.
*
* The goals of this storage class is to provide failover, redudancy and load
* balancing. This is accomplished by storing the data object to several
* groups of memcache servers. Each data object is replicated to every group
* of memcache servers, but it is only stored to one server in each group.
*
* For this code to work correctly, all web servers accessing the data must
* have the same clock (as measured by the time()-function). Different clock
* values will lead to incorrect behaviour.
/**
* This class provides a class with behaviour similar to the $_SESSION variable.
* Data is automatically saved on exit.
*
* @author Olav Morken, UNINETT AS.
* @package simpleSAMLphp
......@@ -34,23 +15,16 @@ require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSA
class SimpleSAML_MemcacheStore {
/* This variable contains the last commit time of this object.
* This is used to determine which of the memcache servers contain
* the newest version.
*
* This variable is serialized.
*/
private $lastCommitTime = NULL;
/* This variable contains the id for this data.
/**
* This variable contains the id for this data.
*
* This variable is serialized.
*/
private $id = NULL;
/* This variable contains an array with all key-value pairs stored
/**
* This variable contains an array with all key-value pairs stored
* in this object.
*
* This variable is serialized.
......@@ -58,7 +32,8 @@ class SimpleSAML_MemcacheStore {
private $data = NULL;
/* This variable contains the serialized data which is currently
/**
* This variable contains the serialized data which is currently
* stored on the memcache servers. By comparing the data which is
* stored against the current data, we can determine whether we
* should update the data.
......@@ -71,115 +46,47 @@ class SimpleSAML_MemcacheStore {
private $savedData = NULL;
/* Private cache of the memcache servers we are using. */
private static $serverGroups = NULL;
/* This function is used to find an existing storage object. It will
* return NULL if no storage object with the given id is found.
*
* Parameters:
* $id The id of the storage object we are looking for. A id
* consists of lowercase alphanumeric characters.
/**
* This function is used to find an existing storage object. It will return NULL if no storage object
* with the given id is found.
*
* Returns:
* The corresponding MemcacheStorage object if the data is found
* or NULL if it isn't found.
* @param $id The id of the storage object we are looking for. A id consists of lowercase
* alphanumeric characters.
* @return The corresponding MemcacheStorage object if the data is found or NULL if it isn't found.
*/
public static function find($id) {
assert(self::isValidID($id));
$mustUpdate = FALSE;
$latest = NULL;
$latestSerializedValue = NULL;
$serializedData = SimpleSAML_Memcache::get($id);
$data = unserialize($serializedData);
/* Search all the servers for the given id. */
foreach(self::getMemcacheServers() as $server) {
$serializedValue = $server->get($id);
if($serializedValue === FALSE) {
/* Either the server is down, or we don't have
* the value stored on that server.
*/
$mustUpdate = TRUE;
continue;
}
/* Deserialize the object. */
$v = unserialize($serializedValue);
/* Make sure that the deserialized object is of the
* correct type.
*/
if(!($v instanceof self)) {
$e = 'We retrieved an object from the' .
' memcache server which wasn\'t an' .
' instance of a MemcacheStore object.' .
' This should never happen, and is a ' .
' sign that the MemcacheStore may be ' .
' unreliable.';
error_log($e);
die($e);
}
if($latest === NULL) {
$latest = $v;
$latestSerializedValue = $serializedValue;
continue;
}
if($latest->lastCommitTime == $v->lastCommitTime) {
/* They were committed at the same time. Assume
* that they are equal.
*/
continue;
}
/* They are different. We need to update at least one
* of them to maintain synchronization.
*/
$mustUpdate = TRUE;
/* Update $latest if $v is newer than $latest. */
if($latest->lastCommitTime < $v->lastCommitTime) {
$latest = $v;
$latestSerializedValue = $serializedValue;
}
if($data === NULL) {
return $NULL;
}
/* Check if we found any stored object matching the id. */
if($latest === NULL) {
if(!($data instanceof self)) {
error_log('Retrieved key from memcache did not contain a MemcacheStore object.');
return NULL;
}
/* If we don't need to update this data object, then we can
* store the serialized value in the MemcacheStore object. If
* we need to update this object, then we don't store the
* serialized value.
*/
if($mustUpdate === FALSE) {
$latest->savedData = $latestSerializedValue;
}
$data->savedData = $serializedData;
/* Add a call to save the data when we exit. */
register_shutdown_function(array($latest, 'save'));
register_shutdown_function(array($data, 'save'));
return $latest;
return $data;
}
/* This constructor is used to create a new storage object. The storage
* object will be created with the specified id and the initial
* content passed in the data argument.
/**
* This constructor is used to create a new storage object. The storage object will be created with the
* specified id and the initial content passed in the data argument.
*
* If there exists a storage object with the specified id, then it will
* be overwritten.
* If there exists a storage object with the specified id, then it will be overwritten.
*
* Parameters:
* $id The id of the storage object.
* $data An array containing the initial data of the storage
* object.
* @param $id The id of the storage object.
* @param $data An array containing the initial data of the storage object.
*/
public function __construct($id, $data = array()) {
/* Validate arguments. */
......@@ -194,22 +101,22 @@ class SimpleSAML_MemcacheStore {
}
/* This magic function is called on serialization of this class.
* It returns a list of the names of the variables which should be
* serialized.
/**
* This magic function is called on serialization of this class. It returns a list of the names of the
* variables which should be serialized.
*
* @return List of variables which should be serialized.
*/
private function __sleep() {
return array('lastCommitTime', 'id', 'data');
return array('id', 'data');
}
/* This function retrieves the specified key from this storage object.
*
* Parameters:
* $key The key we should retrieve the value of.
/**
* This function retrieves the specified key from this storage object.
*
* Returns:
* The value of the specified key, or NULL of the key wasn't found.
* @param $key The key we should retrieve the value of.
* @return The value of the specified key, or NULL of the key wasn't found.
*/
public function get($key) {
if(!array_key_exists($key, $this->data)) {
......@@ -220,12 +127,12 @@ class SimpleSAML_MemcacheStore {
}
/* This function sets the specified key to the specified value in this
/**
* This function sets the specified key to the specified value in this
* storage object.
*
* Parameters:
* $key The key we should set.
* $value The value we should set the key to.
* @param $key The key we should set.
* @param $value The value we should set the key to.
*/
public function set($key, $value) {
$this->data[$key] = $value;
......@@ -238,15 +145,15 @@ class SimpleSAML_MemcacheStore {
}
/* This function determines whether we need to update the data which
/**
* This function determines whether we need to update the data which
* is stored on the memcache servers.
*
* If we are unable to detect a change, then we will serialize the
* class and compare this to the data we have cached. We do this to
* determine if any of the references have changed.
*
* Returns:
* TRUE if this object needs an update, FALSE if not.
* @return TRUE if this object needs an update, FALSE if not.
*/
private function needUpdate() {
/* If $savedData is NULL, then we don't have any data stored
......@@ -308,299 +215,35 @@ class SimpleSAML_MemcacheStore {
return FALSE;
}
/* We need to store the updated value to the servers. */
return TRUE;
}
/* This function stores this storage object to the memcache servers.
/**
* This function stores this storage object to the memcache servers.
*/
public function save() {
/* First, chech whether we need to store new data. */
if(!$this->needUpdate()) {
/* This object is unchanged - we don't need to
* commit.
*/
/* This object is unchanged - we don't need to commit. */
return;
}
/* Update the last-commit timestamp. */
$this->lastCommitTime = time();
/* Calculate the value we should store on the servers. */
/* Serialize this object. */
$this->savedData = serialize($this);
/* Store this object to all groups of memcache servers. */
foreach(self::getMemcacheServers() as $server) {
$server->set($this->id, $this->savedData, 0,
self::getExpireTime());
}
}
/* This function adds a server from the 'memcache_store.servers'
* configuration option to a Memcache object.
*
* Parameters:
* $memcache The Memcache object we should add this server to.
* $server The server we should parse. This is an array with
* the following keys:
* - hostname
* Hostname or ip address to the memcache server.
* - port (optional)
* port number the memcache server is running on. This
* defaults to memcache.default_port if no value is given.
* The default value of memcache.default_port is 11211.
* - weight (optional)
* The weight of this server in the load balancing
* cluster.
* - timeout (optional)
* The timeout for contacting this server, in seconds.
* The default value is 3 seconds.
*/
private static function addMemcacheServer($memcache, $server) {
/* The hostname option is required. */
if(!array_key_exists('hostname', $server)) {
$e = 'hostname setting missing from server in the' .
' \'memcache_store.servers\' configuration' .
' option.';
error_log($e);
die($e);
}
$hostname = $server['hostname'];
/* The hostname must be a valid string. */
if(!is_string($hostname)) {
$e = 'Invalid hostname for server in the' .
' \'memcache_store.servers\' configuration' .
' option. The hostname is supposed to be a' .
' string.';
error_log($e);
die($e);
}
/* Check if the user has specified a port number. */
if(array_key_exists('port', $server)) {
/* Get the port number from the array, and validate
* it.
*/
$port = (int)$server['port'];
if(($port < 0) || ($port > 65535)) {
$e = 'Invalid port for server in the' .
' \'memcache_store.servers\'' .
' configuration option. The port number' .
' is supposed to be an integer between' .
' 0 and 65535.';
error_log($e);
die($e);
}
} else {
/* Use the default port number from the ini-file. */
$port = (int)ini_get('memcache.default_port');
if($port <= 0 || $port > 65535) {
/* Invalid port number from the ini-file.
* fall back to the default.
*/
$port = 11211;
}
}
/* Check if the user has specified a weight for this server. */
if(array_key_exists('weight', $server)) {
/* Get the weight and validate it. */
$weight = (int)$server['weight'];
if($weight <= 0) {
$e = 'Invalid weight for server in the' .
' \'memcache_store.servers\'' .
' configuration option. The weight is' .
' supposed to be a positive integer.';
error_log($e);
die($e);
}
} else {
/* Use a default weight of 1. */
$weight = 1;
}
/* Check if the user has specified a timeout for this
* server.
*/
if(array_key_exists('timeout', $server)) {
/* Get the timeout and validate it. */
$timeout = (int)$server['timeout'];
if($timeout <= 0) {
$e = 'Invalid timeout for server in the' .
' \'memcache_store.servers\'' .
' configuration option. The timeout is' .
' supposed to be a positive integer.';
error_log($e);
die($e);
}
} else {
/* Use a default timeout of 3 seconds. */
$timeout = 3;
}
/* Add this server to the Memcache object. */
$memcache->addServer($hostname, $port, TRUE, $weight, $timeout);
}
/* This function takes in a list of servers belonging to a group and
* creates a Memcache object from the servers in the group.
*
* Parameters:
* $group Array of servers. Each server is represented by one array
* with the hostname (and optionally port number, timeout,
* ...). See the addMemcacheServer function for more
* information.
*
* Returns:
* A Memcache object of the servers in the group.
*/
private static function loadMemcacheServerGroup($group) {
/* Create the Memcache object. */
$memcache = new Memcache();
if($memcache == NULL) {
$e = 'Unable to create an instance of a Memcache' .
' object. Is the memcache extension' .
' installed?';
error_log($e);
die($e);
}
/* Iterate over all the servers in the group and add them to
* the Memcache object.
*/
foreach($group as $index => $server) {
/* Make sure that we don't have an index. An index
* would be a sign of invalid configuration.
*/
if(!is_int($index)) {
$e = 'Invalid index on element in the' .
' \'memcache_store.servers\'' .
' configuration option. Perhaps you' .
' have forgotten to add an array(...)' .
' around one of the server groups? The' .
' invalid index was: ' . $index;
error_log($e);
die($e);
}
/* Make sure that the server object is an array. Each
* server is an array with name-value pairs.
*/
if(!is_array($server)) {
$e = 'Invalid value for the server with' .
' index ' . $index . '. Remeber that' .
' the \'memcache_store.servers\'' .
' configuration option contains an' .
' array of arrays of arrays.';
error_log($e);
die($e);
}
self::addMemcacheServer($memcache, $server);
}
return $memcache;
}
/* This function gets a list of all configured memcache servers. This
* list is initialized based on the content of
* 'memcache_store.servers' in the configuration.
*
* Returns:
* Array with Memcache objects.
*/
private static function getMemcacheServers() {
/* Check if we have loaded the servers already. */
if(self::$serverGroups != NULL) {
return self::$serverGroups;
}
/* Initialize the servers-array. */
self::$serverGroups = array();
/* Load the configuration. */
$config = SimpleSAML_Configuration::getInstance();
assert($config instanceof SimpleSAML_Configuration);
$groups = $config->getValue('memcache_store.servers');
/* Validate the 'memcache_store.servers' configuration
* option.
*/
if(is_null($groups)) {
$e = 'Unable to get value of the' .
' \'memcache_store.servers\' configuration' .
' option.';
error_log($e);
die($e);
}
if(!is_array($groups)) {
$e = 'The value of the \'memcache_store.servers\'' .
' configuration option isn\'t an array.';
error_log($e);
die($e);
}
/* Iterate over all the groups in the
* 'memcache_store.servers' configuration option.
*/
foreach($groups as $index => $group) {
/* Make sure that the group doesn't have an index.
* An index would be a sign of invalid configuration.
*/
if(!is_int($index)) {
$e = 'Invalid index on element in the' .
' \'memcache_store.servers\'' .
' configuration option. Perhaps you' .
' have forgotten to add an array(...)' .
' around one of the server groups? The' .
' invalid index was: ' . $index;
error_log($e);
die($e);
}
/* Make sure that the group is an array. Each group
* is an array of servers. Each server is an array of
* name => value pairs for that server.
*/
if(!is_array($group)) {
$e = 'Invalid value for the server with' .
' index ' . $index . '. Remeber that' .
' the \'memcache_store.servers\'' .
' configuration option contains an' .
' array of arrays of arrays.';
error_log($e);
die($e);
}
/* Parse and add this group to the server group list.
*/
self::$serverGroups[] =
self::loadMemcacheServerGroup($group);
}
return self::$serverGroups;
/* Write to the memcache servers. */
SimpleSAML_Memcache::set($this->id, $this->savedData);
}
/* This function determines whether the argument is a valid id.
/**
* This function determines whether the argument is a valid id.
* A valid id is a string containing lowercase alphanumeric
* characters.
*
* Parameters:
* $id The id we should validate.
*
* Returns:
* TRUE if the id is valid, FALSE otherwise.
* @param $id The id we should validate.
* @return TRUE if the id is valid, FALSE otherwise.
*/
private static function isValidID($id) {
if(!is_string($id)) {
......@@ -618,90 +261,5 @@ class SimpleSAML_MemcacheStore {
return TRUE;
}
/* This is a helper-function which returns the expire value of data
* we should store to the memcache servers.
*
* The value is set depending on the configuration. If no value is
* set in the configuration, then we will use a default value of 0.
* 0 means that the item will never expire.
*
* Returns:
* The value which should be passed in the set(...) calls to the
* memcache objects.
*/
private static function getExpireTime()
{
/* Get the configuration instance. */
$config = SimpleSAML_Configuration::getInstance();
assert($config instanceof SimpleSAML_Configuration);
/* Get the expire-value from the configuration. */
$expire = $config->getValue('memcache_store.expires');
/* If 'memcache_store.expires' isn't defined in the
* configuration, then we will use 0 as the expire parameter.
*/
if($expire === NULL) {
return 0;
}
/* The 'memcache_store.expires' option must be an integer. */
if(!is_integer($expire)) {
$e = 'The value of \'memcache_store.expires\' in the' .
' configuration must be a valid integer.';
error_log($e);
die($e);
}
/* It must be a positive integer. */
if($expire < 0) {
$e = 'The value of \'memcache_store.expires\' in the' .
' configuration can\'t be a negative integer.';
error_log($e);
die($e);
}
/* If the configuration option is 0, then we should
* return 0. This allows the user to specify that the data
* shouldn't expire.
*/
if($expire == 0) {
return 0;
}
/* The expire option is given as the number of seconds into the
* future an item should expire. We convert this to an actual
* timestamp.
*/
$expireTime = time() + $expire;
return $expireTime;
}
/**
* This function retrieves statistics about all memcache server groups.
*
* @return Array with the names of each stat and an array with the value for each
* server group.
*/
public static function getStats()
{
$ret = array();
foreach(self::getMemcacheServers() as $sg) {
$stats = $sg->getExtendedStats();
if($stats === FALSE) {
throw new Exception('Failed to get memcache server status.');
}
$stats = SimpleSAML_Utilities::transposeArray($stats);
$ret = array_merge_recursive($ret, $stats);
}
return $ret;
}
}
?>
?>
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment