Skip to content

Commit

Permalink
Add a cache to store UUID -> Fedora 4 path mappings (#41)
Browse files Browse the repository at this point in the history
* First draft of a cache for UUID -> F4 paths in transactions.

* Update interface and implementation methods

* New tests for KeyCache

* Fixing up Redis

* Correct response is +PONG

* PSR-2 compliance and function mocking case-sensitivity.
  • Loading branch information
whikloj authored and ruebot committed May 19, 2016
1 parent cc865fc commit aaebb0f
Show file tree
Hide file tree
Showing 6 changed files with 358 additions and 2 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
},
"require-dev": {
"phpunit/phpunit": "^4.8",
"squizlabs/php_codesniffer": "^2.0@dev"
"squizlabs/php_codesniffer": "^2.0@dev",
"mockery/mockery": "^0.9"
},
"license": "GPLv3",
"authors": [
Expand Down
3 changes: 3 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,8 @@
<testsuite name="uuid">
<directory>test/UuidGenerator</directory>
</testsuite>
<testsuite name="cache">
<directory>test/KeyCache</directory>
</testsuite>
</testsuites>
</phpunit>
81 changes: 81 additions & 0 deletions src/KeyCache/IUuidCache.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php
/**
* @file
* Part of the Chullo service
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Islandora\Chullo\KeyCache;

/**
* Interface for key -> value service to store UUID -> Fedora Paths
* not yet indexed into the triplestore (ie. in a transaction)
* @author Jared Whiklo <[email protected]>
* @since 2016-04-12
*/
interface IUuidCache
{

/**
* Sets the value for the provided key.
*
* @var string $txID
* The transaction ID.
* @var string $uuid
* The UUID.
* @var string $path
* The Fedora path.
* @var int $expire
* The number of seconds from now to expire. Default to an hour.
* @return bool
* Was key set successful.
*/
public function set($txID, $uuid, $path, $expire = 3600);

/**
* Gets the Fedora path for the provided UUID.
*
* @var string $txID
* The transaction ID.
* @var string $uuid
* The UUID
* @return string
* The UUID corresponding to the path in the transaction or NULL
*/
public function getByUuid($txID, $uuid);

/**
* Gets the UUID for the provided path.
*
* @var string $txID
* The transaction ID.
* @var string $path
* The key.
* @return string
* The UUID corresponding to the path in the transaction or NULL
*/
public function getByPath($txID, $path);

/**
* Delete any pairs related to transaction ID.
*
* @var string $txID
* The transaction ID.
* @return void
*/
public function delete($txID);

/**
* Set/reset the transaction to expire in $seconds seconds.
*
* @var string $txID
* The transaction ID.
* @var int $seconds
* The number of seconds from now to expire.
* @return bool
* Whether the expiry was set or not.
*/
public function extend($txID, $seconds);
}
161 changes: 161 additions & 0 deletions src/KeyCache/RedisKeyCache.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
<?php
/**
* @file
* This is part of Chullo service.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Islandora\Chullo\KeyCache;

/**
* Implementation of the IKeyCache using Redis and phpredis.
*
* @author Jared Whiklo <[email protected]>
* @since 2016-04-12
*/
class RedisKeyCache implements IUuidCache
{

/**
* @var Redis $redis
* The Redis object.
*/
protected $redis;
/**
* @var bool $connected
* Are we connected to the Redis server yet.
*/
private $connected = false;
/**
* @var string $host
* The hostname of the redis server.
*/
private $host;
/**
* @var int $port
* The port of the redis server.
*/
private $port;

/**
* @param \Redis $instance
* An instance of the Redis client
*/
public function __construct(\Redis $instance, $host, $port)
{
$this->redis = $instance;
$this->redis->host = $host;
$this->redis->port = $port;
}

/**
*
*/
public function __destruct()
{
$this->redis->close();
}

/**
* Create using a hostname and port.
*
* @param $host
* The hostname of the redis server
* @param $port
* The port of the redis server
* @return RedisKeyCache
*/
public static function create($host = "localhost", $port = 6379)
{
$redis = new \Redis();
$redis->connect($host, $port);
return new RedisKeyCache($redis, $host, $port);
}

/**
* Check if we are connected to the Redis server.
*
* @return bool Whether we are connected.
*/
private function isConnected()
{
try {
$result = $this->redis->ping();
$connected = ($result == "+PONG");
} catch (RedisException $e) {
error_log("RedisKeyCache: Error communicating with Redis server - " . $e->getMessage());
# Try connecting once more.
$this->redis->connect($this->host, $this->port);
$result = $this->redis->ping();
$connected = ($result == "+PONG");
}
return $connected;
}

/**
* {@inheritdoc}
*/
public function set($txID, $uuid, $path, $expire = 3600)
{
if ($this->isConnected()) {
$result = $this->redis->hSetNx($txID, $uuid, $path);
if ($result) {
$this->redis->expire($txID, $expire);
}
return $result;
}
/**
* TODO: return exceptions??
*/
return false;
}

/**
* {@inheritdoc}
*/
public function getByUuid($txId, $uuid)
{
if ($this->isConnected()) {
return $this->redis->hget($txId, $uuid);
}
}

/**
* {@inheritdoc}
*/
public function getByPath($txId, $path)
{
if ($this->isConnected()) {
$hashes = $this->redis->hgetall($txId);
if ($hashes !== false) {
$flipped = array_flip($hashes);
if (array_key_exists($path, $flipped)) {
return $flipped[$path];
}
}
}
return false;
}

/**
* {@inheritdoc}
*/
public function extend($txId, $expire = 3600)
{
if ($this->isConnected()) {
return $this->redis->expire($txId, $expire);
}
}

/**
* {@inheritdoc}
*/
public function delete($txId)
{
if ($this->isConnected()) {
return $this->redis->del($txId);
}
}
}
110 changes: 110 additions & 0 deletions test/KeyCache/UuidCacheTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php

namespace Islandora\Chullo\KeyCache;

use Islandora\Chullo\Uuid\UuidGenerator;
use Islandora\Chullo\KeyCache\RedisKeyCache;
use Mockery\Adapter\Phpunit\MockeryTestCase;

class UuidCacheTest extends \PHPUnit_Framework_TestCase
{

protected $redis;

protected $uuid_gen;

public function setUp()
{
$this->redis = \Mockery::mock('\Redis');
$this->redis->shouldReceive('expire')->andReturn(1);
$this->redis->shouldReceive('ping')->andReturn("+PONG");
$this->redis->shouldReceive('close')->andReturn(null);

$this->uuid_gen = new UuidGenerator();
}

/**
* @covers RedisKeyCache::set
* @group UnitTest
*/
public function testAddUuidPair()
{
// Need both to account for OS X and Linux differences.
$this->redis->shouldReceive('hSetNx')->andReturn(1);
$this->redis->shouldReceive('hsetnx')->andReturn(1);
$redis_cache = new RedisKeyCache($this->redis, 'localhost', 6379);

$transId = "tx:" . $this->uuid_gen->generateV4();
$uuid = $this->uuid_gen->generateV4();
$path = "http://localhost:8080/fcrepo/rest/object1";

$this->assertEquals(1, $redis_cache->set($transId, $uuid, $path), "Error setting uuid->path hash");
}

/**
* @covers RedisKeyCache::getByUuid
* @group UnitTest
*/
public function testGetByUuid()
{
$txID = $transId = "tx:" . $this->uuid_gen->generateV4();
$uuid = $this->uuid_gen->generateV4();
$path = "http://localhost:8080/fcrepo/rest/object1";

// Need both to account for OS X and Linux differences.
$this->redis->shouldReceive('hGet')->with($txID, $uuid)->andReturn($path);
$this->redis->shouldReceive('hget')->with($txID, $uuid)->andReturn($path);

$redis_cache = new RedisKeyCache($this->redis, 'localhost', 6379);

$this->assertEquals($path, $redis_cache->getByUuid($txID, $uuid), "Error getting by Uuid");
}

/**
* @covers RedisKeyCache::getByPath
* @group UnitTest
*/
public function testGetByPath()
{
$txID = $transId = "tx:" . $this->uuid_gen->generateV4();

$uuid1 = $this->uuid_gen->generateV4();
$path1 = "http://localhost:8080/fcrepo/rest/object1";

$uuid2 = $this->uuid_gen->generateV4();
$path2 = "http://localhost:8080/fcrepo/rest/object2";

$hashes = array(
$uuid1 => $path1,
$uuid2 => $path2,
);

// Need both to account for OS X and Linux differences.
$this->redis->shouldReceive('hGetAll')->with($txID)->andReturn($hashes);
$this->redis->shouldReceive('hgetall')->with($txID)->andReturn($hashes);

$redis_cache = new RedisKeyCache($this->redis, 'localhost', 6379);

$this->assertEquals($uuid2, $redis_cache->getByPath($txID, $path2), "Error getting by Path");
}

/**
* @covers RedisKeyCache::delete
* @group UnitTest
*/
public function testDelete()
{
$txID = $transId = "tx:" . $this->uuid_gen->generateV4();

$this->redis->shouldReceive('del')->with($txID)->andReturn(1);

$redis_cache = new RedisKeyCache($this->redis, 'localhost', 6379);

$this->assertEquals(1, $redis_cache->delete($txID), "Error deleting transaction ID.");
}

public function tearDown()
{
\Mockery::close();
}
}
2 changes: 1 addition & 1 deletion test/UuidGenerator/UuidTest.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace Islandora\Chullo;
namespace Islandora\Chullo\Uuid;

use Ramsey\Uuid\Uuid;
use Islandora\Chullo\Uuid\UuidGenerator;
Expand Down

0 comments on commit aaebb0f

Please sign in to comment.