Skip to content

Commit

Permalink
Merge pull request #339 from aol/Issue-316-AddElasticacheSupport
Browse files Browse the repository at this point in the history
Issue 316 add elasticache support
  • Loading branch information
morrowgi authored May 15, 2017
2 parents 04d8f52 + 909601c commit 42b86af
Show file tree
Hide file tree
Showing 9 changed files with 348 additions and 0 deletions.
86 changes: 86 additions & 0 deletions micro-elasticache/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
apply plugin: 'groovy'
apply plugin: 'java'
repositories {}

description = 'micro-elasticache'
dependencies {
compile group: 'com.amazonaws', name: 'aws-java-sdk-elasticache', version: '1.11.126'
compile group: 'net.spy', name: 'spymemcached', version: '2.12.3'
compile project(':micro-core')
compile project(':micro-guava')
testCompile group: 'org.codehaus.groovy', name: 'groovy-all', version:'2.3.3'
testCompile(group: 'org.spockframework', name: 'spock-core', version:'0.7-groovy-2.0') { exclude(module: 'groovy-all') }
testCompile group: 'com.cyrusinnovation', name: 'mockito-groovy-support', version:'1.3'
testCompile 'cglib:cglib-nodep:2.2'
testCompile group: 'org.hamcrest', name: 'hamcrest-all', version:hamcrestVersion
testCompile project(':micro-grizzly-with-jersey')
}

sourceSets {
main {
java { srcDirs = ['src/main/java']}
resources { srcDir 'src/main/resources' }
}

test {
java { srcDirs = []}
groovy { srcDirs = ['src/test/java'] }
resources { srcDir 'src/test/resources' }
}

}

modifyPom {
project {
name 'Microserver elasticache'
description 'Opinionated rest microservices'
url 'https://github.com/aol/micro-server'
inceptionYear '2015'

groupId 'com.aol.microservices'
artifactId 'micro-elasticache'
version "$version"


scm {
url 'scm:[email protected]:aol/micro-server.git'
connection 'scm:[email protected]:aol/micro-server.git'
developerConnection 'scm:[email protected]:aol/micro-server.git'
}

licenses {
license {
name 'The Apache Software License, Version 2.0'
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
distribution 'repo'
}
}

developers {
developer {
id 'johnmcclean-aol'
name 'John McClean'
email '[email protected]'
}
developer {
id 'morrowgi'
name 'Gordon Morrow'
email '[email protected]'
}
}

}
}

extraArchive {
sources = true
tests = true
javadoc = true
}

nexus {
sign = true
repositoryUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2'
snapshotRepositoryUrl = 'https://oss.sonatype.org/content/repositories/snapshots'
}

34 changes: 34 additions & 0 deletions micro-elasticache/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Elasticache plugin for BASE microservices

[micro-elasticache example apps](https://github.com/aol/micro-server/tree/master/micro-elasticache/src/test/java/app)

Basically Available Soft statE

* Simple Memcached Client (ElastiCache as distributed / persistent map)


## Configurable properties

Key used to store data used by the configured ManfiestComparator in Couchbase (default is default-key)

elasticache.hostname is configuration endpoint of the elasticache cluster
elasticache.portis the port of the elasticache cluster
elasticache.retry.after.seconds is the number of seconds between each retry
elasticache.max.retries is the maximum number of retries before client throws error and gives up


## Getting The Microserver Couchbase Plugin


### Maven
```xml
<dependency>
<groupId>com.aol.microservices</groupId>
<artifactId>micro-elasticache</artifactId>
<version>x.yz</version>
</dependency>
```
### Gradle
```groovy
compile 'com.aol.microservices:micro-elasticache:x.yz'
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.aol.micro.server.elasticache;



import lombok.extern.slf4j.Slf4j;
import net.spy.memcached.*;

import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;


import net.spy.memcached.auth.AuthDescriptor;
import net.spy.memcached.auth.PlainCallbackHandler;
import net.spy.memcached.MemcachedClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;

import java.net.InetSocketAddress;

import java.util.List;
import java.util.Optional;

@Slf4j
@Configuration
public class ConfigureElasticache {


private final String hostname;
private final int port;
private final int retryAfterSecs;
private final int maxRetries;

@Autowired
public ConfigureElasticache( @Value("${elasticache.hostname:null}") String hostname,
@Value("${elasticache.port:6379}") int port,
@Value("${elasticache.retry.after.seconds:1}") int retryAfterSecs,
@Value("${elasticache.max.retries:3}") int maxRetries) {
this.hostname = hostname;
this.port = port;
this.retryAfterSecs = retryAfterSecs;
this.maxRetries = maxRetries;
}


@Bean(name = "transientCache")
public DistributedCacheManager transientCache() throws IOException, URISyntaxException {
try {
log.info("Creating Memcached Data connection for elasticache cluster: {}", hostname);
return new TransientElasticacheDataConnection(createMemcachedClient(), retryAfterSecs, maxRetries);
}
catch (Exception e) {
log.error("Failed to create transient data connection", e);
return null;
}
}

@Bean(name = "memcachedClient")
public MemcachedClient createMemcachedClient() throws IOException {
try {
log.info("Starting an instance of memcache client towards elasticache cluster");
return new MemcachedClient(new InetSocketAddress(hostname, port));
} catch (IOException e) {
log.error("Could not initilise connection to elasticache cluster", e);
return null;
}

}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.aol.micro.server.elasticache;
import java.util.Optional;

public interface DistributedCacheManager<V> {
void setConnectionTested(boolean result);
boolean isAvailable();
boolean add(String key, int exp, V value);
Optional<V> get(String key);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.aol.micro.server.elasticache;

import com.aol.cyclops.data.collections.extensions.persistent.PSetX;
import com.aol.micro.server.Plugin;

public class ElasticachePlugin implements Plugin {

@Override
public PSetX<Class> springClasses() {
return PSetX.of(ConfigureElasticache.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.aol.micro.server.elasticache;

import lombok.extern.slf4j.Slf4j;

import java.util.Optional;
import net.spy.memcached.MemcachedClient;
import lombok.extern.slf4j.Slf4j;


@Slf4j
public class TransientElasticacheDataConnection<V> implements DistributedCacheManager<V> {

private volatile boolean available = false;
private final MemcachedClient memcachedClient;
private final int retryAfterSec;
private final int maxTry;

public TransientElasticacheDataConnection(MemcachedClient memcachedClient,int retryAfterSec, int maxTry) {
this.memcachedClient = memcachedClient;
this.retryAfterSec = retryAfterSec;
this.maxTry = maxTry;
}

@Override
public boolean add(final String key, int exp, final Object value) {

log.trace("Memcached add operation on key '{}', with value:{}", key, value);
boolean success = false;
int tryCount = 0;

do {
try {
if (tryCount > 0) {
Thread.sleep(retryAfterSec * 1000);
log.warn("retrying operation #{}", tryCount);
}
tryCount++;
success = memcachedClient.add(key, exp, value)
.get();
} catch (final Exception e) {
log.warn("memcache set: {}", e.getMessage());
}
} while (!success && tryCount < maxTry);

if (!success) {
log.error("Failed to add key to Elasticache {}", key);
}
if (success && tryCount > 1) {
log.info("Connection restored OK to Elasticache cluster");
}

available = success;
return success;
}

@Override
public Optional<V> get(String key) {
return (Optional<V>) Optional.ofNullable(memcachedClient.get(key));
}

@Override
public boolean isAvailable() {
return available;
}

@Override
public final void setConnectionTested(final boolean available) {
this.available = available;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
com.aol.micro.server.elasticache.ElasticachePlugin
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.aol.micros.server.elasticache;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.stub;
import static org.junit.Assert.assertEquals;
import com.aol.micro.server.elasticache.TransientElasticacheDataConnection;
import net.spy.memcached.internal.OperationFuture;
import org.junit.Before;
import org.junit.Test;
import net.spy.memcached.MemcachedClient;
import java.util.Optional;

public class TransientElasticacheDataConnectionTest {

MemcachedClient memcachedClient;

@Before
public void setup() {
memcachedClient = mock(MemcachedClient.class);

stub(memcachedClient.get("key1")).toReturn("value1");
stub(memcachedClient.get("key2")).toReturn("value2");
OperationFuture<Boolean> mockedFuture = mock(OperationFuture.class);
stub(memcachedClient.add("keyAdd", 3600, "valueadd")).toReturn(mockedFuture);
}

@Test
public void happyPathGetTest() {
TransientElasticacheDataConnection transientClient = new TransientElasticacheDataConnection(memcachedClient, 3, 1);
assertEquals(Optional.ofNullable("value1"), transientClient.get("key1"));
assertEquals(Optional.ofNullable("value2"), transientClient.get("key2"));
}

@Test
public void notExistingKeyGetTest() {
TransientElasticacheDataConnection transientClient = new TransientElasticacheDataConnection(memcachedClient, 3, 1);
assertEquals(Optional.empty(), transientClient.get("key3"));
}

@Test
public void notExistingKeyPutTest() {
TransientElasticacheDataConnection transientClient = new TransientElasticacheDataConnection(memcachedClient, 3, 1);
assertEquals(false, transientClient.add("keyAdd", 3600, "valueadd"));
}

@Test
public void testIsAvailableFalse() {
TransientElasticacheDataConnection transientClient = new TransientElasticacheDataConnection(memcachedClient, 3, 1);
transientClient.setConnectionTested(false);
assertEquals(false, transientClient.isAvailable());
}

@Test
public void testIsAvailableTrue() {
TransientElasticacheDataConnection transientClient = new TransientElasticacheDataConnection(memcachedClient, 3, 1);
transientClient.setConnectionTested(true);
assertEquals(true, transientClient.isAvailable());
}



}
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ include ':micro-event-metrics'
include ':micro-metrics-datadog'
include ':micro-log-streamer'
include ':micro-jmx-metrics'
include ':micro-elasticache'

0 comments on commit 42b86af

Please sign in to comment.