/*
* Copyright 2009-2010 Joubin Houshyar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jredis.cluster;
import java.util.HashMap;
import java.util.Map;
import org.jredis.NotSupportedException;
import org.jredis.ri.alphazero.connection.DefaultConnectionSpec;
import org.jredis.ri.alphazero.support.Log;
import org.jredis.ri.cluster.DefaultClusterNodeSpec;
import org.jredis.test.util.RunningAverage;
import org.testng.annotations.Test;
import static org.jredis.cluster.ClusterSuiteTestData.*;
import static org.testng.Assert.*;
/**
* [TODO: document me!]
*
* @author joubin (alphazero@sensesay.net)
* @date Mar 29, 2010
*
*/
//@Test(suiteName="extensions-cluster-model")
public abstract class ClusterModelProviderTestBase extends RefImplTestSuiteBase<ClusterModel> {
// ------------------------------------------------------------------------
// Extension point
// ------------------------------------------------------------------------
/**
* @param clusterSpec
* @return
*/
protected abstract ClusterModel newClusterModel (ClusterSpec clusterSpec) ;
/**
* @return a {@link ClusterSpec} suitable for instantiating a provider instance
*/
protected abstract ClusterSpec newClusterSpec () ;
/**
* @return the ClusterSpec.Type that the provider is supposed to support
*/
protected abstract ClusterType getSupportedClusterType ();
// ------------------------------------------------------------------------
// Specification Interface tested
// ------------------------------------------------------------------------
/* (non-Javadoc) @see org.jredis.cluster.ProviderTestBase#getSpecificationClass() */
@Override
protected Class<?> getSpecificationClass () {
return ClusterModel.class;
}
/* (non-Javadoc) @see org.jredis.cluster.ProviderTestBase#newProviderInstance() */
@Override
protected ClusterModel newProviderInstance () {
return newClusterModel(newClusterSpec());
}
// ------------------------------------------------------------------------
// Test general contract of SPECS for Cluster and its Nodes
// ------------------------------------------------------------------------
@Test
public void metaTest (){
Log.log("[META] test the test suite assumptions!");
ClusterType clusterType = getSupportedClusterType();
assertNotNull(clusterType, "getSupportedClusterType");
ClusterSpec testSpec = newClusterSpec();
assertNotNull(testSpec, "newClusterSpec should not return null");
assertEquals(testSpec.getType(), clusterType, "asserted supported cluster type and type from the newClusterSpec should be the same");
ClusterModel model = newProviderInstance();
assertNotNull(model, "newProviderInstance should not return null");
}
@Test
public void testClusterSpecPropertyOps () {
Log.log("test ClusterSpec accessors");
// null spec arg on construct must raise an error
//
boolean didRaiseEx;
didRaiseEx = false;
try {
@SuppressWarnings("unused")
ClusterModel m = newClusterModel(null);
}
catch (IllegalArgumentException e) { didRaiseEx = true; }
catch (RuntimeException whatsthis) { fail("unexpected exception raised during op", whatsthis); }
assertTrue(didRaiseEx, "IllegalArgumentException raise is expected");
// spec arg with no nodes on construct is not an error if
// model supports reconfiguration
//
boolean supportsReconfig = newProviderInstance().supportsReconfiguration();
didRaiseEx = false;
try {
ClusterSpec s = newClusterSpec();
s.removeAll(s.getNodeSpecs());
assertTrue(s.getNodeSpecs().size() == 0, "cluster spec should have no node specs now");
newClusterModel(s);
}
catch (IllegalArgumentException e) { didRaiseEx = true; }
catch (RuntimeException whatsthis) { fail("unexpected exception raised during op", whatsthis); }
assertTrue(didRaiseEx && !supportsReconfig , "expected only if non reconfigurable");
ClusterSpec spec = newClusterSpec();
ClusterModel model = newClusterModel(spec);
assertNotNull(newProviderInstance().getSpec(), "clusterSpec property should be non-null");
assertEquals(model.getSpec(), spec, "expecting returned property to be the spec used in constructor");
}
@Test
public void testReconfigOfNonReconfigurableModel () {
Log.log("test reconfiguration for static configuration models");
// skip the test if not applicable to this provider
//
if(provider.supportsReconfiguration()) {
Log.log("Skipping test; not applicable.");
return;
}
// create a new model, get its spec, and pick one node to remove
// this should raise an exception
ClusterModel model = newProviderInstance();
ClusterSpec clusterSpec = provider.getSpec();
ClusterNodeSpec nodeSpec = null;
for(ClusterNodeSpec n : clusterSpec.getNodeSpecs()){
nodeSpec = n;
break;
}
boolean didRaiseEx;
didRaiseEx = false;
try {
model.removeNode(nodeSpec);
}
catch (NotSupportedException e) { didRaiseEx = true; }
catch (RuntimeException whatsthis) { fail("unexpected exception raised during op", whatsthis); }
assertTrue(didRaiseEx, "NotSupportedException raise is expected");
// so far so good.
// now lets add a node
didRaiseEx = false;
try {
model.addNode(new DefaultClusterNodeSpec(DefaultConnectionSpec.newSpec()));
}
catch (NotSupportedException e) { didRaiseEx = true; }
catch (RuntimeException whatsthis) { fail("unexpected exception raised during op", whatsthis); }
assertTrue(didRaiseEx, "NotSupportedException raise is expected");
}
@Test
public void testReconfigOfReconfigurableModel () {
Log.log("test reconfiguration for dynamic configuration models");
// skip the test if not applicable to this provider
//
if(!provider.supportsReconfiguration()) {
Log.log("Skipping test; not applicable.");
return;
}
// create a new model, get its spec, and pick one node to remove
//
ClusterModel model = newProviderInstance();
ClusterSpec clusterSpec = provider.getSpec();
ClusterNodeSpec nodeSpec = null;
for(ClusterNodeSpec n : clusterSpec.getNodeSpecs()){
nodeSpec = n;
break;
}
model.removeNode(nodeSpec);
// now lets add a node
//
model.addNode(new DefaultClusterNodeSpec(DefaultConnectionSpec.newSpec().setPort(9999)));
}
@Test
public void testKeyDistribution (){
long keycnt = data.MEDIUM_CNT;
Log.log("test key distribution with " + keycnt + " keys");
// get the node for a number of keys and check the distribution
// across the nodes. Very difficult to have a definitive tests here
// without spec'ing distribution metric (e.g. limits on std-dev), but
// at least all nodes should have keys asigned to them, as a general start.
Map<ClusterNodeSpec, Long> distribution = new HashMap<ClusterNodeSpec, Long>();
ClusterModel model = newProviderInstance();
assertNotNull(model, "newProviderInstance should not return null");
// we simply count the keys assigned to each node
//
for(int i=0; i<keycnt; i++){
String key = getRandomAsciiString(512);
byte[] keybytes = key.getBytes();
ClusterNodeSpec nodeSpec = model.getNodeForKey(keybytes);
Long cnt = distribution.get(nodeSpec);
cnt = cnt == null ? 1 : cnt.longValue()+1;
distribution.put(nodeSpec, cnt);
}
// and now lets do some basic analysis
// we'll need the ClusterSpec to get the nodes
ClusterSpec clusterSpec = provider.getSpec();
assertNotNull(clusterSpec, "cluster spec must not be null");
RunningAverage avg = new RunningAverage();
int nodeCnt = clusterSpec.getNodeSpecs().size();
Number[] data = new Number[nodeCnt];
int i = 0;
for(ClusterNodeSpec n : clusterSpec.getNodeSpecs()){
Long cnt = distribution.get(n);
avg.onMeasure(cnt);
data[i++] = cnt;
}
assertTrue(avg.getMin() > 0, "No node should have zero keys assigned to it");
assertTrue(avg.getMax() > 0, "No node should have zero keys assigned to it");
Log.log("Distributed %d keys in a %d node cluster:\n\t Key/node distribution -- AVG: %d - MIN: %d - MAX: %d\n", keycnt, nodeCnt, avg.get(), avg.getMin(), avg.getMax());
}
}