/*
* Copyright (c) 2008-2014 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.db.server;
import static org.junit.Assert.*;
import java.beans.PropertyDescriptor;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.client.URIUtil;
import com.emc.storageos.db.client.constraint.AlternateIdConstraint;
import com.emc.storageos.db.client.constraint.Constraint;
import com.emc.storageos.db.client.constraint.ContainmentConstraint;
import com.emc.storageos.db.client.constraint.ContainmentPermissionsConstraint;
import com.emc.storageos.db.client.constraint.ContainmentPrefixConstraint;
import com.emc.storageos.db.client.constraint.NamedElementQueryResultList;
import com.emc.storageos.db.client.constraint.PrefixConstraint;
import com.emc.storageos.db.client.constraint.URIQueryResultList;
import com.emc.storageos.db.client.constraint.impl.AlternateIdConstraintImpl;
import com.emc.storageos.db.client.constraint.impl.ContainmentConstraintImpl;
import com.emc.storageos.db.client.constraint.impl.ContainmentLabelConstraintImpl;
import com.emc.storageos.db.client.constraint.impl.ContainmentPermissionsConstraintImpl;
import com.emc.storageos.db.client.constraint.impl.DecommissionedConstraintImpl;
import com.emc.storageos.db.client.constraint.impl.LabelConstraintImpl;
import com.emc.storageos.db.client.impl.AltIdDbIndex;
import com.emc.storageos.db.client.impl.ColumnField;
import com.emc.storageos.db.client.impl.DataObjectType;
import com.emc.storageos.db.client.impl.DbClientContext;
import com.emc.storageos.db.client.impl.DbIndex;
import com.emc.storageos.db.client.impl.DecommissionedDbIndex;
import com.emc.storageos.db.client.impl.NamedRelationDbIndex;
import com.emc.storageos.db.client.impl.PermissionsDbIndex;
import com.emc.storageos.db.client.impl.PrefixDbIndex;
import com.emc.storageos.db.client.impl.RelationDbIndex;
import com.emc.storageos.db.client.impl.ScopedLabelDbIndex;
import com.emc.storageos.db.client.impl.TypeMap;
import com.emc.storageos.db.client.model.*;
import com.emc.storageos.db.common.VdcUtil;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.SSTableExport;
// RaceCondition test is to ensure even any rarely happens race condition do occur, DB is still left in an "acceptable" state
// "acceptable" here means:
// 1) cross field data is still somewhat consistency
// 2) index CF is consistent with object CF
// 3) no zombie index entries
// 4) other
@SuppressWarnings("pmd:ArrayIsStoredDirectly")
abstract class ObjectModifier<T extends DataObject> implements Runnable
{
// The synchronization object used by outside to communicate
private final DbClientTest.StepLock stepLock;
private Integer[] readySync;
private DbClientTest.DbClientImplUnitTester dbClient;
private Class<T> clazz;
public volatile URI objId;
protected Object context;
protected DataObjectType doType;
public ObjectModifier() {
this.stepLock = null;
}
public ObjectModifier(Class<? extends DataObject> clazz, DbClientTest.DbClientImplUnitTester dbClient, Integer[] readySync,
Object context) {
this.clazz = (Class<T>) clazz;
this.doType = TypeMap.getDoType(clazz);
this.dbClient = dbClient;
this.readySync = readySync;
this.context = context;
this.stepLock = new DbClientTest.StepLock();
this.stepLock.clientId = this.readySync[0]++;
this.stepLock.step = DbClientTest.StepLock.Step.Pre;
}
public void moveToState(DbClientTest.StepLock.Step step) {
this.stepLock.moveToState(step);
}
@Override
public void run() {
// Initialize per thread step lock for DbClient
dbClient.threadStepLock.set(this.stepLock);
for (;;) {
// Notify main thread that this thread is ready
synchronized (this.readySync) {
this.readySync[0]--;
this.readySync.notify();
}
// Wait for read green light
if (!this.stepLock.waitForStep(DbClientTest.StepLock.Step.Read)) {
return;
}
try {
// Now, load the object from DB
T obj = this.dbClient.queryObject(this.clazz, this.objId);
this.stepLock.ackStep(DbClientTest.StepLock.Step.Read);
// Apply modification to object
modify(obj);
// Go to persist
this.dbClient.persistObject(obj);
} finally {
// Here we need to ensure we consumed all states
this.stepLock.drain(DbClientTest.StepLock.Step.CleanupOldColumns);
}
}
}
public abstract void modify(T obj);
}
interface IndexVerifier {
public void verify(Class<? extends DataObject> clazz, URI id, DbClient client);
}
class SingleFieldIndexVerifier implements IndexVerifier {
String fieldName;
public SingleFieldIndexVerifier(String fieldName) {
this.fieldName = fieldName;
}
@Override
public void verify(Class<? extends DataObject> clazz, URI id, DbClient client) {
// Get meta data about the object, field and index we're going to test
DataObjectType doType = TypeMap.getDoType(clazz);
ColumnField field = doType.getColumnField(fieldName);
if (field == null) {
throw new NullPointerException(String.format("Cannot find field %s from class %s", fieldName, clazz.getSimpleName()));
}
DbIndex index = field.getIndex();
// Load object first
DataObject obj = (DataObject) client.queryObject(clazz, id);
// Get current value of indexed field
Object val = null;
try {
val = field.getPropertyDescriptor().getReadMethod().invoke(obj);
} catch (IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
Assert.fail("Cannot get field value");
}
boolean indexByKey = field.getPropertyDescriptor().getReadMethod().getAnnotation(IndexByKey.class) != null;
// See which type the index is, and call corresponding verification functions
if (index instanceof AltIdDbIndex) {
verifyAltIdDbIndex(obj, field, val, indexByKey, client);
} else if (index instanceof RelationDbIndex) {
verifyRelationDbIndex(obj, field, val, indexByKey, client);
} else if (index instanceof NamedRelationDbIndex) {
verifyNamedRelationDbIndex(obj, field, val, indexByKey, client);
} else if (index instanceof PrefixDbIndex) {
verifyPrefixDbIndex(obj, field, val, indexByKey, client);
} else if (index instanceof DecommissionedDbIndex) {
verifyDecommissionedDbIndex(obj, field, val, indexByKey, client);
} else if (index instanceof PermissionsDbIndex) {
verifyPermissionsDbIndex(obj, field, val, indexByKey, client);
} else if (index instanceof ScopedLabelDbIndex) {
verifyScopedLabelDbIndex(obj, field, val, indexByKey, client);
} else {
System.out.printf("Unsupported index type %s%n", index.getClass().getSimpleName());
}
}
private void verifyContain(Constraint c, URI uri, int count, DbClient client) {
URIQueryResultList list = new URIQueryResultList();
client.queryByConstraint(c, list);
int realCount = 0;
boolean found = false;
for (URI elem : list) {
realCount++;
if (uri != null && uri.equals(elem)) {
found = true;
}
}
if (uri != null) {
assertTrue(found);
}
if (count >= 0) {
assertTrue(String.format("Found %d URIs while %d is expected", realCount, count), realCount == count);
}
}
private void verifyAltIdDbIndex(DataObject obj, ColumnField field, Object val, boolean indexByKey, DbClient client) {
switch (field.getType()) {
case Primitive: {
AlternateIdConstraint constraint = new AlternateIdConstraintImpl(field, (String) val);
verifyContain(constraint, obj.getId(), -1, client);
}
break;
case TrackingMap:
for (Map.Entry entry : ((AbstractChangeTrackingMap<?>) val).entrySet())
{
Object altId = indexByKey ? entry.getKey() : entry.getValue();
AlternateIdConstraint constraint = new AlternateIdConstraintImpl(field, (String) altId);
verifyContain(constraint, obj.getId(), -1, client);
}
break;
case TrackingSet:
for (String key : (AbstractChangeTrackingSet<String>) val)
{
AlternateIdConstraint constraint = new AlternateIdConstraintImpl(field, key);
verifyContain(constraint, obj.getId(), -1, client);
}
break;
case Id:
case NamedURI:
case NestedObject:
case TrackingSetMap:
default:
throw new IllegalArgumentException(String.format("Field type %s is not supported by AltIdDbIndex", field.getType()
.toString()));
}
}
private void verifyRelationDbIndex(DataObject obj, ColumnField field, Object val, boolean indexByKey, DbClient client) {
switch (field.getType()) {
case Primitive: {
ContainmentConstraint constraint = new ContainmentConstraintImpl((URI) val, obj.getClass(), field);
verifyContain(constraint, obj.getId(), -1, client);
}
break;
case TrackingMap:
for (String key : ((AbstractChangeTrackingMap<String>) val).keySet())
{
ContainmentConstraint constraint = new ContainmentConstraintImpl(URI.create(key), obj.getClass(), field);
verifyContain(constraint, obj.getId(), -1, client);
}
break;
case TrackingSet:
for (String key : (AbstractChangeTrackingSet<String>) val)
{
ContainmentConstraint constraint = new ContainmentConstraintImpl(URI.create(key), obj.getClass(), field);
verifyContain(constraint, obj.getId(), -1, client);
}
break;
case Id:
case NamedURI:
case NestedObject:
case TrackingSetMap:
default:
throw new IllegalArgumentException(String.format("Field type %s is not supported by RelationDbIndex", field.getType()
.toString()));
}
}
private void verifyNamedRelationDbIndex(DataObject obj, ColumnField field, Object val, boolean indexByKey, DbClient client) {
switch (field.getType()) {
case NamedURI: {
NamedURI namedUriVal = (NamedURI) val;
ContainmentLabelConstraintImpl constraint = new ContainmentLabelConstraintImpl(namedUriVal.getURI(), namedUriVal.getName(),
field);
verifyContain(constraint, obj.getId(), -1, client);
}
break;
case Id:
case NestedObject:
case Primitive:
case TrackingMap:
case TrackingSet:
case TrackingSetMap:
default:
throw new IllegalArgumentException(String.format("Field type %s is not supported by NamedRelationDbIndex", field.getType()
.toString()));
}
}
private void verifyPrefixDbIndex(DataObject obj, ColumnField field, Object val, boolean indexByKey, DbClient client) {
switch (field.getType()) {
case Primitive: {
LabelConstraintImpl constraint = new LabelConstraintImpl((String) val, field);
verifyContain(constraint, obj.getId(), -1, client);
}
break;
case Id:
case NestedObject:
case NamedURI:
case TrackingMap:
case TrackingSet:
case TrackingSetMap:
default:
throw new IllegalArgumentException(String.format("Field type %s is not supported by PrefixDbIndex", field.getType()
.toString()));
}
}
private void verifyDecommissionedDbIndex(DataObject obj, ColumnField field, Object val, boolean indexByKey, DbClient client) {
switch (field.getType()) {
case Primitive: {
DecommissionedConstraintImpl constraint = new DecommissionedConstraintImpl(obj.getClass(), field, (boolean) val);
verifyContain(constraint, obj.getId(), -1, client);
}
break;
case Id:
case NamedURI:
case NestedObject:
case TrackingMap:
case TrackingSet:
case TrackingSetMap:
default:
throw new IllegalArgumentException(String.format("Field type %s is not supported by DecommissionedDbIndex", field.getType()
.toString()));
}
}
private void verifyPermissionsDbIndex(DataObject obj, ColumnField field, Object val, boolean indexByKey, DbClient client) {
switch (field.getType()) {
case TrackingSetMap:
for (String key : ((AbstractChangeTrackingSetMap<String>) val).keySet())
{
ContainmentPermissionsConstraintImpl constraint = new ContainmentPermissionsConstraintImpl(key, field, obj.getClass());
NamedElementQueryResultList results = new NamedElementQueryResultList();
client.queryByConstraint(constraint, results);
HashSet<String> setFromIndex = new HashSet<String>();
for (NamedElementQueryResultList.NamedElement elem : results) {
if (elem.getId().equals(obj.getId())) {
setFromIndex.add(elem.getName());
}
}
AbstractChangeTrackingSet<String> values = ((AbstractChangeTrackingSetMap<String>) val).get(key);
assertTrue("The value set from index is not same as what is currently in object", setFromIndex.equals(values));
}
break;
case Id:
case NamedURI:
case NestedObject:
case Primitive:
case TrackingMap:
case TrackingSet:
default:
throw new IllegalArgumentException(String.format("Field type %s is not supported by PermissionsDbIndex", field.getType()
.toString()));
}
}
private void verifyScopedLabelDbIndex(DataObject obj, ColumnField field, Object val, boolean indexByKey, DbClient client) {
switch (field.getType()) {
case TrackingSet:
for (ScopedLabel scopedLabel : (AbstractChangeTrackingSet<ScopedLabel>) val)
{
LabelConstraintImpl constraint = new LabelConstraintImpl(URI.create(scopedLabel.getScope()), scopedLabel.getLabel(),
field);
verifyContain(constraint, obj.getId(), -1, client);
}
break;
case Id:
case NamedURI:
case NestedObject:
case Primitive:
case TrackingMap:
case TrackingSetMap:
default:
throw new IllegalArgumentException(String.format("Field type %s is not supported by ScopedLabelDbIndex", field.getType()
.toString()));
}
}
}
// This class holds the simple test cases that blindly set predefined values to single field,
// complex test cases please use ObjectModifier<> directly instead.
@SuppressWarnings("pmd:ArrayIsStoredDirectly")
class IndexTestData {
public String name; // Name of the test case
// Which object type to test
public Class<? extends DataObject> clazz;
// Initial state
// Primitive
// "field=", value
// "field=", null
// Set
// "field<", key
// "field>", key
// Map
// "field<", key, value
// "field>", key
// MapSet
// "field<", key1, key2
// "field>", key1, key2
public Object[] initial;
// Multiple concurrent changes
public Object[][] modifiers;
public IndexVerifier verifier;
public IndexTestData(String name, Class<? extends DataObject> clazz, Object[] initial, Object[][] modifiers, IndexVerifier verifier) {
this.name = name;
this.clazz = clazz;
this.initial = initial;
this.modifiers = modifiers;
this.verifier = verifier;
}
public static boolean isEqual(DataObject obj, Object[] ops)
{
DataObject shouldBe = createInitial(obj.getClass(), obj.getId(), ops);
// Get DataObjectType
DataObjectType doType = TypeMap.getDoType(obj.getClass());
for (ColumnField field : doType.getColumnFields()) {
if (!shouldBe.isChanged(field.getName())) {
continue;
}
Method readMethod = field.getPropertyDescriptor().getReadMethod();
try {
Object shouldBeVal = readMethod.invoke(shouldBe);
Object realVal = readMethod.invoke(obj);
if (!equalsObject(realVal, shouldBeVal)) {
return false;
}
} catch (IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return false;
}
}
return true;
}
public static boolean equalsObject(Object a, Object b) {
if (a instanceof Map || b instanceof Map) {
return equalsMap((Map) a, (Map) b);
}
if (a instanceof Set && ((Set<?>) a).isEmpty()) {
a = null;
}
if (b instanceof Set && ((Set<?>) b).isEmpty()) {
b = null;
}
return a == null || b == null ? a == b : a.equals(b);
}
// Compare two Maps, null values are treated as no such key
private static <K, V> boolean equalsMap(Map<K, V> a, Map<K, V> b) {
if (a != null) {
for (Map.Entry<K, V> entry : a.entrySet()) {
V valB = b == null ? null : b.get(entry.getKey());
if (!equalsObject(entry.getValue(), valB)) {
return false;
}
}
}
if (b != null) {
for (Map.Entry<K, V> entry : b.entrySet()) {
if (entry.getValue() != null && (a == null || !a.containsKey(entry.getKey()))) {
return false;
}
}
}
return true;
}
public static DataObject createInitial(Class<? extends DataObject> clazz, URI id, Object[] ops) {
DataObject obj;
try {
obj = clazz.getConstructor(new Class<?>[0]).newInstance(new Object[0]);
} catch (InstantiationException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
obj.setId(id);
apply(obj, TypeMap.getDoType(clazz), ops);
return obj;
}
// Returns a list of fields touched
public static void apply(DataObject obj, DataObjectType doType, Object[] ops) {
try {
for (int i = 0; i < ops.length; i++) {
if (!(ops[i] instanceof String)) {
throw new IllegalArgumentException();
}
String op = (String) ops[i];
// Get name of the field
String fieldName = op.substring(0, op.length() - 1);
op = op.substring(op.length() - 1, op.length());
// Get type of the field
ColumnField field = doType.getColumnField(fieldName);
PropertyDescriptor propDesc = field.getPropertyDescriptor();
switch (field.getType()) {
case Primitive:
case NamedURI:
case Id:
case NestedObject:
if (op.charAt(0) != '=') {
throw new IllegalArgumentException();
}
propDesc.getWriteMethod().invoke(obj, ops[++i]);
break;
case TrackingSet:
if (op.charAt(0) == '<') {
AbstractChangeTrackingSet set = (AbstractChangeTrackingSet) propDesc.getReadMethod().invoke(obj);
if (set == null) {
set = (AbstractChangeTrackingSet) propDesc.getPropertyType().getConstructor(new Class<?>[0])
.newInstance(new Object[0]);
propDesc.getWriteMethod().invoke(obj, set);
}
set.add(ops[++i]);
} else if (op.charAt(0) == '>') {
AbstractChangeTrackingSet set = (AbstractChangeTrackingSet) propDesc.getReadMethod().invoke(obj);
set.remove(ops[++i]);
} else if (op.charAt(0) == '=') {
propDesc.getWriteMethod().invoke(obj, ops[++i]);
}
break;
case TrackingMap:
if (op.charAt(0) == '<') {
AbstractChangeTrackingMap map = (AbstractChangeTrackingMap) propDesc.getReadMethod().invoke(obj);
if (map == null) {
map = (AbstractChangeTrackingMap) propDesc.getPropertyType().getConstructor(new Class<?>[0])
.newInstance(new Object[0]);
propDesc.getWriteMethod().invoke(obj, map);
}
map.put((String) ops[i + 1], ops[i + 2]);
i += 2;
} else if (op.charAt(0) == '>') {
AbstractChangeTrackingMap map = (AbstractChangeTrackingMap) propDesc.getReadMethod().invoke(obj);
map.remove((String) ops[++i]);
} else if (op.charAt(0) == '=') {
propDesc.getWriteMethod().invoke(obj, ops[++i]);
}
break;
case TrackingSetMap:
if (op.charAt(0) == '<') {
AbstractChangeTrackingSetMap setMap = (AbstractChangeTrackingSetMap) propDesc.getReadMethod().invoke(obj);
if (setMap == null) {
setMap = (AbstractChangeTrackingSetMap) propDesc.getPropertyType().getConstructor(new Class<?>[0])
.newInstance(new Object[0]);
propDesc.getWriteMethod().invoke(obj, setMap);
}
setMap.put((String) ops[i + 1], ops[i + 2]);
i += 2;
} else if (op.charAt(0) == '>') {
AbstractChangeTrackingSetMap setMap = (AbstractChangeTrackingSetMap) propDesc.getReadMethod().invoke(obj);
setMap.remove((String) ops[i + 1], ops[i + 2]);
i += 2;
} else if (op.charAt(0) == '=') {
propDesc.getWriteMethod().invoke(obj, ops[++i]);
}
break;
default:
break;
}
}
} catch (IllegalArgumentException
| IllegalAccessException | InvocationTargetException | InstantiationException | NoSuchMethodException | SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
abstract class OperationSequenceGenerator {
int objCount;
int opCount;
long readOnlyOpsMask;
int[] nextOps;
int[] generatedSequence;
int generatedCount;
int targetCount;
int generatedSequenceCount;
public OperationSequenceGenerator(int objCount, int opCount, long readOnlyOpsMask) {
this.objCount = objCount;
this.opCount = opCount;
this.readOnlyOpsMask = readOnlyOpsMask;
this.nextOps = new int[objCount];
this.targetCount = objCount * opCount;
this.generatedSequence = new int[this.targetCount * 2];
this.generatedCount = 0;
}
public void generate() {
// Choose each object as start
for (int i = 0; i < this.objCount; i++) {
if (this.nextOps[i] == this.opCount) {
continue;
}
// Found one new operation, however, for read operations
if (this.generatedCount > 0 && (this.readOnlyOpsMask & (1 << this.nextOps[i])) != 0) {
int prevObj = this.generatedSequence[this.generatedCount * 2 - 2];
int prevOp = this.generatedSequence[this.generatedCount * 2 - 1];
if ((this.readOnlyOpsMask & (1 << prevOp)) != 0 && (i < prevObj || this.nextOps[i] < prevOp)) {
continue;
}
}
this.generatedSequence[this.generatedCount * 2 + 0] = i;
this.generatedSequence[this.generatedCount * 2 + 1] = this.nextOps[i]++;
if (++this.generatedCount == this.targetCount) { // Done one generation
onSequence(this.generatedSequenceCount++, this.generatedSequence);
} else {
generate();
}
this.nextOps[i]--;
this.generatedCount--;
}
}
protected abstract void onSequence(int index, int[] sequence);
}
public class DbIndexTest extends DbsvcTestBase {
private static final Logger _logger = LoggerFactory.getLogger(DbIndexTest.class);
private DbClientTest.DbClientImplUnitTester _dbClient;
private NodeProbe probe;
static {
_startJmx = true;
}
private DbClientTest.DbClientImplUnitTester createClient() {
DbClientTest.DbClientImplUnitTester dbClient = new DbClientTest.DbClientImplUnitTester();
dbClient.setCoordinatorClient(_coordinator);
dbClient.setDbVersionInfo(sourceVersion);
dbClient.setBypassMigrationLock(true);
_encryptionProvider.setCoordinator(_coordinator);
dbClient.setEncryptionProvider(_encryptionProvider);
DbClientContext localCtx = new DbClientContext();
localCtx.setClusterName("Test");
localCtx.setKeyspaceName("Test");
dbClient.setLocalContext(localCtx);
return dbClient;
}
@Before
public void setupTest() throws IOException {
DbClientTest.DbClientImplUnitTester dbClient = createClient();
VdcUtil.setDbClient(dbClient);
dbClient.setBypassMigrationLock(false);
dbClient.start();
_dbClient = dbClient;
// this.probe = new NodeProbe("127.0.0.1", 7199);
}
@After
public void teardown() {
if (_dbClient instanceof DbClientTest.DbClientImplUnitTester) {
((DbClientTest.DbClientImplUnitTester) _dbClient).removeAll();
}
}
@SuppressWarnings("unchecked")
private void testRaceCondition(final IndexTestData test) throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
final Integer[] readySync = new Integer[] { 0 };
// Allocate threads
final ObjectModifier[] modifiers = new ObjectModifier[test.modifiers.length];
final Thread[] threads = new Thread[test.modifiers.length];
for (int i = 0; i < test.modifiers.length; i++) {
modifiers[i] = new ObjectModifier(test.clazz, this._dbClient, readySync, (Object) test.modifiers[i]) {
@Override
public void modify(DataObject obj) {
Object[] ops = (Object[]) this.context;
IndexTestData.apply(obj, this.doType, ops);
}
};
threads[i] = new Thread(modifiers[i]);
}
// Start each thread
for (int i = 0; i < threads.length; i++) {
threads[i].start();
}
final DbClientTest.StepLock.Step[] steps = new DbClientTest.StepLock.Step[]
{
DbClientTest.StepLock.Step.Read,
DbClientTest.StepLock.Step.InsertNewColumns,
DbClientTest.StepLock.Step.FetchNewestColumns,
DbClientTest.StepLock.Step.CleanupOldColumns
};
OperationSequenceGenerator generator = new OperationSequenceGenerator(modifiers.length, steps.length, 9) {
@Override
protected void onSequence(int index, int[] sequence) {
// Wait until all thread done their initialization
synchronized (readySync) {
while (readySync[0] > 0) {
try {
readySync.wait();
} catch (InterruptedException e) {
_logger.warn("Thread is interrupted", e);
}
}
readySync[0] = threads.length;
// Reset sync number for next round
}
// Create initial object
DataObject initObj = IndexTestData.createInitial(test.clazz, URIUtil.createId(test.clazz), test.initial);
_dbClient.createObject(initObj);
// Notify each modifier about the new Id
for (int i = 0; i < modifiers.length; i++) {
modifiers[i].objId = initObj.getId();
}
// Run the sequence
for (int i = 0; i < sequence.length / 2; i++) {
int objOrdinal = sequence[i * 2 + 0];
int opOrdinal = sequence[i * 2 + 1];
modifiers[objOrdinal].moveToState(steps[opOrdinal]);
}
// Now, verify the object is consistent with its index
test.verifier.verify(initObj.getClass(), initObj.getId(), _dbClient);
// TODO: Verify there's no other way to reach the object in same index
// Cleanup, delete the object
// _dbClient.removeObject(initObj);
// TODO: Verify the index CF is empty
}
};
generator.generate();
// Notify all threads to quit
for (int i = 0; i < modifiers.length; i++) {
modifiers[i].moveToState(DbClientTest.StepLock.Step.Quit);
}
for (int i = 0; i < threads.length; i++) {
try {
threads[i].join();
} catch (InterruptedException e) {
_logger.warn("Thread is interrupted", e);
}
}
}
@Test
public void testIndexRaceCondition() throws InstantiationException, IllegalAccessException, IllegalArgumentException,
InvocationTargetException, NoSuchMethodException, SecurityException {
// We're going to need synchronize between threads calling into DbClient
this._dbClient.threadStepLock = new ThreadLocal<DbClientTest.StepLock>();
URI vol1Uri = URIUtil.createId(Volume.class);
URI varr1Uri = URIUtil.createId(VirtualArray.class);
IndexTestData[] tests = new IndexTestData[]
{
new IndexTestData
(
"Test AltIdDbIndex on String field",
Volume.class,
new Object[] { "personality=", "abc" }, // Initial state
new Object[][]
{
new Object[] { "personality=", "def" },
new Object[] { "personality=", "ghi" },
},
new SingleFieldIndexVerifier("personality")
),
new IndexTestData
(
"Test AltIdDbIndex on StringSet field",
AuthnProvider.class,
new Object[] { "domains<", "abc" }, // Initial state
new Object[][]
{
new Object[] { "domains>", "abc" },
new Object[] { "domains<", "def" },
},
new SingleFieldIndexVerifier("domains")
),
new IndexTestData
(
"Test AltIdDbIndex on StringMap field",
Network.class,
new Object[] { "endpoints<", "abc", "123" }, // Initial state
new Object[][]
{
new Object[] { "endpoints>", "abc" },
new Object[] { "endpoints<", "def", "456" },
},
new SingleFieldIndexVerifier("endpoints")
),
new IndexTestData
(
"Test PrefixDbIndex",
Cluster.class,
new Object[] { "label=", "abc" }, // Initial state
new Object[][]
{
new Object[] { "label=", "def" },
new Object[] { "label=", "ghi" },
},
new SingleFieldIndexVerifier("label")
),
new IndexTestData
(
"Test RelationDbIndex on URI field",
Host.class,
new Object[] { "cluster=", URIUtil.createId(Cluster.class) }, // Initial state
new Object[][]
{
new Object[] { "cluster=", URIUtil.createId(Cluster.class) },
new Object[] { "cluster=", URIUtil.createId(Cluster.class) },
},
new SingleFieldIndexVerifier("cluster")
),
new IndexTestData
(
"Test RelationDbIndex on StringMap field",
ExportGroup.class,
new Object[] { "volumes<", vol1Uri.toString(), "111" }, // Initial state
new Object[][]
{
new Object[] { "volumes>", vol1Uri.toString() },
new Object[] { "volumes<", URIUtil.createId(Volume.class).toString(), "222" },
},
new SingleFieldIndexVerifier("volumes")
),
new IndexTestData
(
"Test RelationDbIndex on StringSet field",
VirtualPool.class,
new Object[] { "virtualArrays<", varr1Uri.toString() }, // Initial state
new Object[][]
{
new Object[] { "virtualArrays>", varr1Uri.toASCIIString() },
new Object[] { "virtualArrays<", URIUtil.createId(VirtualArray.class).toString() },
},
new SingleFieldIndexVerifier("virtualArrays")
),
new IndexTestData
(
"Test NamedRelationDbIndex",
BlockMirror.class,
new Object[] { "source=", new NamedURI(URIUtil.createId(Volume.class), "abcde") }, // Initial state
new Object[][]
{
new Object[] { "source=", new NamedURI(URIUtil.createId(Volume.class), "fghij") },
new Object[] { "source=", new NamedURI(URIUtil.createId(Volume.class), "klmno") },
},
new SingleFieldIndexVerifier("source")
),
// new IndexTestData
// (
// "Test DecommissionedDbIndex",
// BlockMirror.class,
// new Object[] {"inactive=", false}, // Initial state
// new Object[][]
// {
// new Object[] {"inactive=", true},
// new Object[] {"inactive=", false},
// },
// new SingleFieldIndexVerifier("inactive")
// ),
new IndexTestData
(
"Test PermissionsDbIndex",
VirtualDataCenter.class,
new Object[] { "role-assignment<", "user1", "role1" }, // Initial state
new Object[][]
{
new Object[] { "role-assignment>", "user1", "role1" },
new Object[] { "role-assignment<", "user2", "role1" },
},
new SingleFieldIndexVerifier("role-assignment")
),
new IndexTestData
(
"Test ScopedLabelDbIndex",
FCEndpoint.class,
new Object[] { "tags<", new ScopedLabel("scope1", "label1") }, // Initial state
new Object[][]
{
new Object[] { "tags>", new ScopedLabel("scope1", "label1") },
new Object[] { "tags<", new ScopedLabel("scope2", "label1") },
},
new SingleFieldIndexVerifier("tags")
),
};
for (int i = 0; i < tests.length; i++) {
testRaceCondition(tests[i]);
}
}
@Test
public void testInactive() throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
NoSuchMethodException, SecurityException {
// We're going to need synchronize between threads calling into DbClient
this._dbClient.threadStepLock = new ThreadLocal<DbClientTest.StepLock>();
URI vol1Uri = URIUtil.createId(Volume.class);
URI varr1Uri = URIUtil.createId(VirtualArray.class);
IndexTestData[] tests = new IndexTestData[]
{
new IndexTestData
(
"Test inactive with other index",
Volume.class,
new Object[] { "personality=", "abc" }, // Initial state
new Object[][]
{
new Object[] { "inactive=", true },
new Object[] { "personality=", "ghi" },
},
new IndexVerifier() {
@Override
public void verify(Class<? extends DataObject> clazz, URI id, DbClient client) {
Volume vol = (Volume) client.queryObject(clazz, id);
String per = vol.getPersonality();
DataObjectType doType = TypeMap.getDoType(clazz);
AlternateIdConstraint constraint = new AlternateIdConstraintImpl(doType.getColumnField("personality"), per);
URIQueryResultList list = new URIQueryResultList();
client.queryByConstraint(constraint, list);
for (URI elem : list) {
assertTrue("The index of .personality should be removed", !elem.equals(id));
}
}
}
),
};
for (int i = 0; i < tests.length; i++) {
testRaceCondition(tests[i]);
}
}
public enum Op {
PrimitiveAdd,
PrimitiveRemove,
SetAdd,
SetRemove,
// Add+Remove, Remove+Add
MapAdd,
MapRemove,
// Add+Remove, Remove+Add, Add+Modify, Add+ModifySame, ...
SetMapAdd, // Add a key to existing set
SetMapRemove, // Remove entire set
}
@SuppressWarnings("pmd:ArrayIsStoredDirectly")
static class TestData {
public String name; // Name of the test case
public Object[] initial; // Initial state in DB that two threads will read
public Object[] rushIn; // this thread will write first
public Object[] primary; // this thread will write last
public Object[] target;
public TestData(String name, Object[] initial, Object[] rushIn, Object[] primary) {
this.name = name;
this.initial = initial;
this.rushIn = rushIn;
this.primary = primary;
}
public TestData(String name, Object[] initial, Object[] rushIn, Object[] primary, Object[] target) {
this.name = name;
this.initial = initial;
this.rushIn = rushIn;
this.primary = primary;
this.target = target;
}
}
static TestData[] testData = new TestData[] {
new TestData("Primitive add same",
null,
new Object[] { Op.PrimitiveAdd, "abc" },
new Object[] { Op.PrimitiveAdd, "abc" }
),
new TestData("Primitive add diff",
null,
new Object[] { Op.PrimitiveAdd, "abc" },
new Object[] { Op.PrimitiveAdd, "def" }
),
new TestData("Primitive upd same",
new Object[] { Op.PrimitiveAdd, "abc" },
new Object[] { Op.PrimitiveAdd, "def" },
new Object[] { Op.PrimitiveAdd, "def" }
),
new TestData("Primitive upd diff",
new Object[] { Op.PrimitiveAdd, "abc" },
new Object[] { Op.PrimitiveAdd, "def" },
new Object[] { Op.PrimitiveAdd, "ghi" }
),
new TestData("Primitive remove",
new Object[] { Op.PrimitiveAdd, "abc", Op.SetAdd, "ghi" },
null,
new Object[] { Op.PrimitiveRemove },
new Object[] { Op.PrimitiveAdd, "abc", Op.SetAdd, "ghi" }
),
new TestData("Set add same",
null,
new Object[] { Op.SetAdd, "abc" },
new Object[] { Op.SetAdd, "abc" } // Should perform no change to DB
),
new TestData("Set add diff",
null,
new Object[] { Op.SetAdd, "abc" },
new Object[] { Op.SetAdd, "def" }
),
new TestData("Set upd same",
new Object[] { Op.SetAdd, "abc" },
null,
new Object[] { Op.SetAdd, "abc" } // Should perform no change to DB
),
new TestData("Set remove same",
new Object[] { Op.SetAdd, "abc", Op.SetAdd, "def" },
new Object[] { Op.SetRemove, "abc" },
new Object[] { Op.SetRemove, "abc" } // Should perform no change to DB
),
new TestData("Set remove diff", // "abc" should not be added back
new Object[] { Op.SetAdd, "abc", Op.SetAdd, "def", Op.SetAdd, "ghi" },
new Object[] { Op.SetRemove, "abc" },
new Object[] { Op.SetRemove, "def" }
),
new TestData("Set remove last",
new Object[] { Op.SetAdd, "abc" },
null,
new Object[] { Op.SetRemove, "abc" }
),
new TestData("Map add same key same value",
null,
new Object[] { Op.MapAdd, "abc", "123" },
new Object[] { Op.MapAdd, "abc", "123" } // Should perform no change to DB
),
new TestData("Map add same key diff value",
null,
new Object[] { Op.MapAdd, "abc", "123" },
new Object[] { Op.MapAdd, "abc", "456" }
),
new TestData("Map add diff key",
null,
new Object[] { Op.MapAdd, "abc", "123" },
new Object[] { Op.MapAdd, "def", "456" }
),
new TestData("Map upd same key same value",
new Object[] { Op.MapAdd, "abc", "123", Op.MapAdd, "def", "456" },
new Object[] { Op.MapAdd, "abc", "789" },
new Object[] { Op.MapAdd, "abc", "789" }
),
new TestData("Map upd same key diff value",
new Object[] { Op.MapAdd, "abc", "123", Op.MapAdd, "def", "456" },
new Object[] { Op.MapAdd, "abc", "789" },
new Object[] { Op.MapAdd, "abc", "012" }
),
new TestData("Map upd diff key",
new Object[] { Op.MapAdd, "abc", "123", Op.MapAdd, "def", "456", Op.MapAdd, "ghi", "789" },
new Object[] { Op.MapAdd, "abc", "123" },
new Object[] { Op.MapAdd, "def", "456" }
),
new TestData("Map del same key",
new Object[] { Op.MapAdd, "abc", "123", Op.MapAdd, "def", "456" },
new Object[] { Op.MapRemove, "abc" },
new Object[] { Op.MapRemove, "abc" }
),
new TestData("Map del diff key",
new Object[] { Op.MapAdd, "abc", "123", Op.MapAdd, "def", "456", Op.MapAdd, "ghi", "789" },
new Object[] { Op.MapRemove, "abc" },
new Object[] { Op.MapRemove, "def" }
),
new TestData("Map del last key",
new Object[] { Op.MapAdd, "abc", "123" },
null,
new Object[] { Op.MapRemove, "abc" }
),
new TestData("SetMap add same key same set",
null,
new Object[] { Op.SetMapAdd, "abc", "123" },
new Object[] { Op.SetMapAdd, "abc", "123" }
),
new TestData("SetMap add diff key same set",
null,
new Object[] { Op.SetMapAdd, "abc", "123" },
new Object[] { Op.SetMapAdd, "abc", "456" }
),
new TestData("SetMap add diff key diff set",
null,
new Object[] { Op.SetMapAdd, "abc", "123" },
new Object[] { Op.SetMapAdd, "def", "456" }
),
new TestData("SetMap remove same key same set",
new Object[] { Op.SetMapAdd, "abc", "123", Op.SetMapAdd, "def", "456", Op.SetMapAdd, "ghi", "123" },
new Object[] { Op.SetMapRemove, "abc", "123" },
new Object[] { Op.SetMapRemove, "abc", "123" }
),
new TestData("SetMap remove diff key same set",
new Object[] { Op.SetMapAdd, "abc", "123", Op.SetMapAdd, "abc", "456", Op.SetMapAdd, "abc", "789" },
new Object[] { Op.SetMapRemove, "abc", "123" },
new Object[] { Op.SetMapRemove, "abc", "123" }
),
new TestData("SetMap remove diff key diff set",
new Object[] { Op.SetMapAdd, "abc", "123", Op.SetMapAdd, "abc", "456", Op.SetMapAdd, "abc", "789" },
new Object[] { Op.SetMapRemove, "abc", "123" },
new Object[] { Op.SetMapRemove, "abc", "123" }
),
new TestData("SetMap remove diff key diff set",
new Object[] { Op.SetMapAdd, "abc", "123", Op.SetMapAdd, "abc", "456", Op.SetMapAdd, "def", "789" },
new Object[] { Op.SetMapRemove, "abc", "123" },
new Object[] { Op.SetMapRemove, "def", "789" }
),
new TestData("SetMap remove last",
new Object[] { Op.SetMapAdd, "abc", "123" },
null,
new Object[] { Op.SetMapRemove, "abc", "123" }
),
};
class IndexTestWorker extends TestWorker {
public IndexTestWorker() {
}
@Override
public void modify(Op op, String seed0, String seed1) {
switch (op) {
case PrimitiveAdd:
this.pool.setLabel(seed0);
break;
case PrimitiveRemove:
this.pool.setLabel(null);
break;
case SetAdd:
if (this.pool.getVirtualArrays() == null) {
this.pool.setVirtualArrays(new StringSet());
}
this.pool.getVirtualArrays().add(seed0);
break;
case SetRemove:
if (this.pool.getVirtualArrays() != null) {
this.pool.getVirtualArrays().remove(seed0);
}
break;
case MapAdd:
if (this.pool.getProtectionVarraySettings() == null) {
this.pool.setProtectionVarraySettings(new StringMap());
}
this.pool.getProtectionVarraySettings().put(seed0, seed1);
break;
case MapRemove:
if (this.pool.getProtectionVarraySettings() != null) {
this.pool.getProtectionVarraySettings().remove(seed0);
}
break;
case SetMapAdd:
if (this.pool.getAcls() == null) {
this.pool.setAcls(new StringSetMap());
}
this.pool.getAcls().put(seed0, seed1);
break;
case SetMapRemove:
if (this.pool.getAcls() != null) {
this.pool.getAcls().remove(seed0, seed1);
}
break;
}
}
@Override
public void verify(TestWorker shouldBe, String caseName) {
VirtualPool p = shouldBe.pool;
// Check the property values are correct
Assert.assertTrue(
String.format("Volume.description does not match, case %s, \"%s\" != \"%s\"", caseName, p.getLabel(),
this.pool.getLabel()), equalsObject(p.getLabel(), this.pool.getLabel()));
Assert.assertTrue(String.format("Volume.protocols does not match, case %s", caseName),
equalsObject(p.getVirtualArrays(), this.pool.getVirtualArrays()));
Assert.assertTrue(String.format("Volume.haVarrayVpoolMap does not match, case %s", caseName),
equalsMap(p.getProtectionVarraySettings(), this.pool.getProtectionVarraySettings()));
Assert.assertTrue(String.format("Volume.arrayInfo does not match, case %s", caseName),
equalsMap(p.getAcls(), this.pool.getAcls()));
}
}
interface Factory<T> {
T create();
}
class TestWorker {
VirtualPool pool;
public TestWorker() {
}
public void create(URI id) {
// Create a volume object for testing
try {
this.pool = (VirtualPool) DataObject.createInstance(VirtualPool.class, id);
this.pool.trackChanges();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
public void load(URI id) {
this.pool = (VirtualPool) _dbClient.queryObject(VirtualPool.class, id);
if (this.pool == null) {
create(id);
}
}
public void modify(Op op, String seed0) {
modify(op, seed0, null);
}
// op determines which property to use
// For property types involves only single value, only seed0 is used
// For property types involves 2 values, both seed0 and seed1 are used.
public void modify(Op op, String seed0, String seed1) {
switch (op) {
case PrimitiveAdd:
this.pool.setDescription(seed0);
break;
case PrimitiveRemove:
this.pool.setDescription(null);
break;
case SetAdd:
if (this.pool.getProtocols() == null) {
this.pool.setProtocols(new StringSet());
}
this.pool.getProtocols().add(seed0);
break;
case SetRemove:
if (this.pool.getProtocols() != null) {
this.pool.getProtocols().remove(seed0);
}
break;
case MapAdd:
if (this.pool.getHaVarrayVpoolMap() == null) {
this.pool.setHaVarrayVpoolMap(new StringMap());
}
this.pool.getHaVarrayVpoolMap().put(seed0, seed1);
break;
case MapRemove:
if (this.pool.getHaVarrayVpoolMap() != null) {
this.pool.getHaVarrayVpoolMap().remove(seed0);
}
break;
case SetMapAdd:
if (this.pool.getArrayInfo() == null) {
this.pool.setArrayInfo(new StringSetMap());
}
this.pool.getArrayInfo().put(seed0, seed1);
break;
case SetMapRemove:
if (this.pool.getArrayInfo() != null) {
this.pool.getArrayInfo().remove(seed0, seed1);
}
break;
}
}
public void save() {
_dbClient.persistObject(this.pool);
this.pool.trackChanges();
}
protected boolean equalsObject(Object a, Object b) {
if (a instanceof Set && ((Set<?>) a).isEmpty()) {
a = null;
}
if (b instanceof Set && ((Set<?>) b).isEmpty()) {
b = null;
}
return a == null || b == null ? a == b : a.equals(b);
}
// Compare two Maps, null values are treated as no such key
protected <K, V> boolean equalsMap(Map<K, V> a, Map<K, V> b) {
if (a != null) {
for (Map.Entry<K, V> entry : a.entrySet()) {
V valB = b == null ? null : b.get(entry.getKey());
if (!equalsObject(entry.getValue(), valB)) {
return false;
}
}
}
if (b != null) {
for (Map.Entry<K, V> entry : b.entrySet()) {
if (entry.getValue() != null && (a == null || !a.containsKey(entry.getKey()))) {
return false;
}
}
}
return true;
}
public void verify(TestWorker shouldBe, String caseName) {
VirtualPool p = shouldBe.pool;
// Check the property values are correct
Assert.assertTrue(
String.format("Volume.description does not match, case %s, \"%s\" != \"%s\"", caseName, p.getDescription(),
this.pool.getDescription()), equalsObject(p.getDescription(), this.pool.getDescription()));
Assert.assertTrue(String.format("Volume.protocols does not match, case %s", caseName),
equalsObject(p.getProtocols(), this.pool.getProtocols()));
Assert.assertTrue(String.format("Volume.haVarrayVpoolMap does not match, case %s", caseName),
equalsMap(p.getHaVarrayVpoolMap(), this.pool.getHaVarrayVpoolMap()));
Assert.assertTrue(String.format("Volume.arrayInfo does not match, case %s", caseName),
equalsMap(p.getArrayInfo(), this.pool.getArrayInfo()));
}
}
public static void applyOperations(TestWorker w, Object[] ops) {
if (ops == null) {
return;
}
for (int i = 0; i < ops.length;) {
Op op = (Op) ops[i++];
String seed0 = null;
if (i < ops.length && !(ops[i] instanceof Op)) {
seed0 = (String) ops[i++];
}
String seed1 = null;
if (i < ops.length && !(ops[i] instanceof Op)) {
seed1 = (String) ops[i++];
}
w.modify(op, seed0, seed1);
}
}
private void runTest(TestData[] tests, Factory<TestWorker> fac) throws InstantiationException, IllegalAccessException {
for (TestData test : tests) {
// Generate unique object ID for this test case, so it will not interact with other test cases
URI id = URIUtil.createId(VirtualPool.class);
// Prepare initial DB state
TestWorker w0 = fac.create();
w0.create(id);
if (test.initial != null) {
applyOperations(w0, test.initial);
}
w0.save();
// Load primary test worker
TestWorker primary = fac.create();
primary.load(id);
// Run rush-in worker if needed
if (test.rushIn != null) {
TestWorker rushIn = fac.create();
rushIn.load(id);
applyOperations(rushIn, test.rushIn);
rushIn.save();
}
// Do primary update and save
applyOperations(primary, test.primary);
primary.save();
// Load final result from DB
TestWorker now = fac.create();
now.load(id);
if (test.target == null) {
applyOperations(w0, test.rushIn);
applyOperations(w0, test.primary);
now.verify(w0, test.name);
}
else {
TestWorker target = fac.create();
target.create(id);
applyOperations(target, test.target);
now.verify(target, test.name);
}
}
}
@Test
public void testFieldChangePersist() throws InstantiationException, IllegalAccessException {
runTest(testData, new Factory<TestWorker>() {
@Override
public TestWorker create() {
return new TestWorker();
}
});
}
@Test
public void testIndexedFieldChangePersist() throws InstantiationException, IllegalAccessException, IllegalArgumentException {
runTest(testData, new Factory<TestWorker>() {
@Override
public TestWorker create() {
return new IndexTestWorker();
}
});
}
private int getTombstoneCount(String cf, String label) throws IOException {
dumpSSTables(cf, label);
FileInputStream fis = new FileInputStream(String.format("%s/data/Test/%s/snapshots/%s/data.json", _dataDir.getAbsolutePath(), cf,
label));
JsonParser parser = new JsonParser();
JsonArray arrRoot = parser.parse(new InputStreamReader(fis)).getAsJsonArray();
int tombstomes = 0;
for (JsonElement elemFile : arrRoot) {
for (JsonElement elemRow : elemFile.getAsJsonArray()) {
JsonObject rowObj = elemRow.getAsJsonObject();
for (JsonElement elemCol : rowObj.getAsJsonArray("columns")) {
JsonArray fieldArr = elemCol.getAsJsonArray();
if (fieldArr.size() > 3 && fieldArr.get(3).getAsString().equals("d")) { // A tombstone
tombstomes++;
}
}
}
}
return tombstomes;
}
private void dumpSSTables(String cf, String label) throws IOException {
String dirPath = String.format("%s/data/Test/%s/snapshots/%s", _dataDir.getAbsolutePath(), cf, label);
File dir = new File(dirPath);
if (!dir.exists()) {
this.probe.takeSnapshot(label, cf, new String[] { "Test" });
}
if (new File(String.format("%s/data.json", dirPath)).exists()) {
return;
}
String[] dataFiles = dir.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith("-Data.db");
}
});
String outFileName = String.format("%s/data.json", dirPath);
PrintStream outs = new PrintStream(outFileName);
outs.println("[");
for (int i = 0; i < dataFiles.length; i++) {
String fileName = dataFiles[i];
Descriptor desc = Descriptor.fromFilename(String.format("%s/%s", dirPath, fileName));
SSTableExport.export(desc, outs, null, null);
if (i + 1 < dataFiles.length) {
outs.println(",");
}
}
outs.println("]");
}
// Commented out for now as the required changed to DbSvcTestBase is not merged from release-2.1 now.
// @Test
public void testTombstoneCount() throws IOException {
URI id = URIUtil.createId(VirtualPool.class);
int tombstoneCount;
{
VirtualPool pool = new VirtualPool();
pool.setId(id);
pool.trackChanges();
pool.setLabel("initial");
_dbClient.persistObject(pool);
tombstoneCount = getTombstoneCount("VirtualPool", "ver0");
}
{
VirtualPool pool = _dbClient.queryObject(VirtualPool.class, id);
pool.setLabel("initial");
_dbClient.persistObject(pool);
int newTombstoneCount = getTombstoneCount("VirtualPool", "ver1");
assertTrue(String.format("Expected tombstone count to be %d, but it's %d", tombstoneCount + 1, newTombstoneCount),
newTombstoneCount == tombstoneCount + 1);
tombstoneCount = newTombstoneCount;
}
{
VirtualPool pool = _dbClient.queryObject(VirtualPool.class, id);
pool.setLabel("changed");
_dbClient.persistObject(pool);
pool = _dbClient.queryObject(VirtualPool.class, id);
assertTrue(pool.getLabel().equals("changed"));
int newTombstoneCount = getTombstoneCount("VirtualPool", "ver2");
assertTrue(String.format("Expected tombstone count to be %d, but it's %d", tombstoneCount + 1, newTombstoneCount),
newTombstoneCount == tombstoneCount + 1);
tombstoneCount = newTombstoneCount;
}
{
VirtualPool pool = _dbClient.queryObject(VirtualPool.class, id);
pool.setLabel("changed");
_dbClient.persistObject(pool);
pool = _dbClient.queryObject(VirtualPool.class, id);
assertTrue(pool.getLabel().equals("changed"));
int newTombstoneCount = getTombstoneCount("VirtualPool", "ver3");
assertTrue(String.format("Expected tombstone count to be %d, but it's %d", tombstoneCount + 1, newTombstoneCount),
newTombstoneCount == tombstoneCount + 1);
tombstoneCount = newTombstoneCount;
}
}
private void verifyContain(Constraint c, URI uri, int count) {
URIQueryResultList list = new URIQueryResultList();
_dbClient.queryByConstraint(c, list);
int realCount = 0;
boolean found = false;
for (URI elem : list) {
realCount++;
if (uri != null && uri.equals(elem)) {
found = true;
}
}
if (uri != null) {
assertTrue(found);
}
if (count >= 0) {
assertTrue(String.format("Found %d URIs while %d is expected", realCount, count), realCount == count);
}
}
@Test
public void testRelationIndex() throws InstantiationException, IllegalAccessException, IOException {
URI id = URIUtil.createId(StoragePool.class);
URI pid0 = URIUtil.createId(StorageSystem.class);
URI pid1 = URIUtil.createId(StorageSystem.class);
ContainmentConstraint constraint0 = ContainmentConstraint.Factory.getContainedObjectsConstraint(pid0, StoragePool.class,
"storageDevice");
ContainmentConstraint constraint1 = ContainmentConstraint.Factory.getContainedObjectsConstraint(pid1, StoragePool.class,
"storageDevice");
{
StoragePool obj = new StoragePool();
obj.setId(id);
obj.setStorageDevice(pid0);
_dbClient.createObject(obj);
}
verifyContain(constraint0, id, 1);
verifyContain(constraint1, null, 0);
{
StoragePool obj = _dbClient.queryObject(StoragePool.class, id);
obj.setStorageDevice(pid1);
_dbClient.persistObject(obj);
}
verifyContain(constraint1, id, 1);
verifyContain(constraint0, null, 0);
}
@Test
public void testRelationIndexSet() {
URI id = URIUtil.createId(ExportGroup.class);
URI pid0 = URIUtil.createId(Snapshot.class);
URI pid1 = URIUtil.createId(Snapshot.class);
ContainmentConstraint constraint0 = ContainmentConstraint.Factory.getContainedObjectsConstraint(pid0, ExportGroup.class,
"snapshots");
ContainmentConstraint constraint1 = ContainmentConstraint.Factory.getContainedObjectsConstraint(pid1, ExportGroup.class,
"snapshots");
{
ExportGroup obj = new ExportGroup();
obj.setId(id);
obj.setSnapshots(new StringSet());
obj.getSnapshots().add(pid0.toString());
_dbClient.createObject(obj);
}
verifyContain(constraint0, id, 1);
verifyContain(constraint1, null, 0);
{
ExportGroup obj = _dbClient.queryObject(ExportGroup.class, id);
obj.getSnapshots().add(pid1.toString());
_dbClient.persistObject(obj);
}
verifyContain(constraint0, id, 1);
verifyContain(constraint1, id, 1);
{
ExportGroup obj = _dbClient.queryObject(ExportGroup.class, id);
// assertTrue(obj.getSnapshots() != null);
obj.getSnapshots().remove(pid0.toString());
_dbClient.persistObject(obj);
}
verifyContain(constraint1, id, 1);
verifyContain(constraint0, null, 0);
}
@Test
public void testRelationIndexMap() {
URI id = URIUtil.createId(ExportGroup.class);
URI pid0 = URIUtil.createId(Volume.class);
URI pid1 = URIUtil.createId(Volume.class);
ContainmentConstraint constraint0 = ContainmentConstraint.Factory.getVolumeExportGroupConstraint(pid0);
ContainmentConstraint constraint1 = ContainmentConstraint.Factory.getVolumeExportGroupConstraint(pid1);
{
ExportGroup obj = new ExportGroup();
obj.setId(id);
obj.setVolumes(new StringMap());
obj.getVolumes().put(pid0.toString(), "1");
_dbClient.createObject(obj);
}
verifyContain(constraint0, id, 1);
verifyContain(constraint1, null, 0);
{
ExportGroup obj = _dbClient.queryObject(ExportGroup.class, id);
obj.getVolumes().put(pid1.toString(), "2");
_dbClient.persistObject(obj);
}
verifyContain(constraint0, id, 1);
verifyContain(constraint1, id, 1);
{
ExportGroup obj = _dbClient.queryObject(ExportGroup.class, id);
obj.getVolumes().remove(pid0.toString());
_dbClient.persistObject(obj);
}
verifyContain(constraint1, id, 1);
verifyContain(constraint0, null, 0);
}
@Test
public void testNamedRelationIndex() {
URI id = URIUtil.createId(Project.class);
URI pid0 = URIUtil.createId(TenantOrg.class);
URI pid1 = URIUtil.createId(TenantOrg.class);
String lbl0 = "abcd1234";
String lbl1 = "efgh5678";
Constraint constraint0 = ContainmentPrefixConstraint.Factory.getProjectUnderTenantConstraint(pid0, lbl0);
Constraint constraint1 = ContainmentPrefixConstraint.Factory.getProjectUnderTenantConstraint(pid1, lbl1);
{
Project obj = new Project();
obj.setId(id);
obj.setLabel(lbl0);
obj.setTenantOrg(new NamedURI(pid0, lbl0));
_dbClient.createObject(obj);
}
verifyContain(constraint0, id, 1);
verifyContain(constraint1, null, 0);
{
Project obj = _dbClient.queryObject(Project.class, id);
obj.setLabel(lbl1);
obj.setTenantOrg(new NamedURI(pid1, lbl1));
_dbClient.persistObject(obj);
}
verifyContain(constraint1, id, 1);
verifyContain(constraint0, null, 0);
}
@Test
public void testAlternateIdIndex() {
URI id = URIUtil.createId(FileShare.class);
String nid0 = UUID.randomUUID().toString();
String nid1 = UUID.randomUUID().toString();
AlternateIdConstraint constraint0 = AlternateIdConstraint.Factory.getFileShareNativeIdConstraint(nid0);
AlternateIdConstraint constraint1 = AlternateIdConstraint.Factory.getFileShareNativeIdConstraint(nid1);
{
FileShare obj = new FileShare();
obj.setId(id);
obj.setNativeGuid(nid0);
_dbClient.createObject(obj);
}
verifyContain(constraint0, id, 1);
verifyContain(constraint1, null, 0);
{
FileShare obj = _dbClient.queryObject(FileShare.class, id);
obj.setNativeGuid(nid1);
_dbClient.persistObject(obj);
}
verifyContain(constraint1, id, 1);
verifyContain(constraint0, null, 0);
}
@Test
public void testDelInactiveAltIdSet() {
URI id = URIUtil.createId(AuthnProvider.class);
String key = "key_to_del";
AuthnProvider obj = new AuthnProvider();
obj.setId(id);
obj.setDomains(new StringSet());
obj.getDomains().add(key);
_dbClient.createObject(obj);
obj = _dbClient.queryObject(AuthnProvider.class, id);
obj.setInactive(true);
_dbClient.persistObject(obj);
obj = _dbClient.queryObject(AuthnProvider.class, id);
obj.getDomains().remove(key);
_dbClient.updateAndReindexObject(obj);
}
@Test
public void testAlternativeIdSetIndex() {
URI id = URIUtil.createId(AuthnProvider.class);
String key0 = "abcd1234";
String key1 = "efgh5678";
AlternateIdConstraint constraint0 = AlternateIdConstraint.Factory.getAuthnProviderDomainConstraint(key0);
AlternateIdConstraint constraint1 = AlternateIdConstraint.Factory.getAuthnProviderDomainConstraint(key1);
{
AuthnProvider obj = new AuthnProvider();
obj.setId(id);
obj.setDomains(new StringSet());
obj.getDomains().add(key0);
_dbClient.createObject(obj);
}
verifyContain(constraint0, id, 1);
verifyContain(constraint1, null, 0);
{
AuthnProvider obj = _dbClient.queryObject(AuthnProvider.class, id);
obj.getDomains().add(key1);
_dbClient.persistObject(obj);
}
verifyContain(constraint0, id, 1);
verifyContain(constraint1, id, 1);
{
AuthnProvider obj = _dbClient.queryObject(AuthnProvider.class, id);
obj.getDomains().remove(key0);
_dbClient.persistObject(obj);
}
verifyContain(constraint1, id, 1);
verifyContain(constraint0, null, 0);
}
@Test
public void testAlternativeIdMapIndex() {
URI id = URIUtil.createId(Network.class);
String key0 = "abcd1234";
String key1 = "efgh5678";
AlternateIdConstraint constraint0 = AlternateIdConstraint.Factory.getEndpointNetworkConstraint(key0);
AlternateIdConstraint constraint1 = AlternateIdConstraint.Factory.getEndpointNetworkConstraint(key1);
{
Network obj = new Network();
obj.setId(id);
obj.setEndpointsMap(new StringMap());
obj.getEndpointsMap().put(key0, "test1");
_dbClient.createObject(obj);
}
verifyContain(constraint0, id, 1);
verifyContain(constraint1, null, 0);
{
Network obj = _dbClient.queryObject(Network.class, id);
obj.getEndpointsMap().put(key1, "test2");
_dbClient.persistObject(obj);
}
verifyContain(constraint0, id, 1);
verifyContain(constraint1, id, 1);
{
Network obj = _dbClient.queryObject(Network.class, id);
obj.getEndpointsMap().remove(key0);
_dbClient.persistObject(obj);
}
verifyContain(constraint1, id, 1);
verifyContain(constraint0, null, 0);
}
@Test
public void testPrefixIndex() {
URI id = URIUtil.createId(Volume.class);
String lbl0 = "abcd1234";
String lbl1 = "efgh5678";
PrefixConstraint constraint0 = PrefixConstraint.Factory.getFullMatchConstraint(Volume.class, "label", lbl0);
PrefixConstraint constraint1 = PrefixConstraint.Factory.getFullMatchConstraint(Volume.class, "label", lbl1);
{
Volume obj = new Volume();
obj.setId(id);
obj.setLabel(lbl0);
_dbClient.createObject(obj);
}
verifyContain(constraint0, id, 1);
verifyContain(constraint1, null, 0);
{
Volume obj = _dbClient.queryObject(Volume.class, id);
obj.setLabel(lbl1);
_dbClient.persistObject(obj);
}
verifyContain(constraint1, id, 1);
verifyContain(constraint0, null, 0);
}
@Test
public void testPermissionIndex() {
URI id = URIUtil.createId(TenantOrg.class);
String key0 = "abcd1234";
String key1 = "efgh5678";
Constraint constraint0 = ContainmentPermissionsConstraint.Factory.getTenantsWithPermissionsConstraint(key0);
Constraint constraint1 = ContainmentPermissionsConstraint.Factory.getTenantsWithPermissionsConstraint(key1);
{
TenantOrg obj = new TenantOrg();
obj.setId(id);
// obj.setLabel("test tenant");
obj.setRoleAssignments(new StringSetMap());
obj.addRole(key0, "role1");
obj.addRole(key0, "role2");
_dbClient.createObject(obj);
}
verifyContain(constraint0, id, 2);
verifyContain(constraint1, null, 0);
{
TenantOrg obj = _dbClient.queryObject(TenantOrg.class, id);
obj.addRole(key1, "role3");
_dbClient.persistObject(obj);
}
verifyContain(constraint0, id, 2);
verifyContain(constraint1, id, 1);
{
TenantOrg obj = _dbClient.queryObject(TenantOrg.class, id);
obj.removeRole(key0, "role1");
_dbClient.persistObject(obj);
}
verifyContain(constraint0, id, 1);
verifyContain(constraint1, id, 1);
{
TenantOrg obj = _dbClient.queryObject(TenantOrg.class, id);
obj.removeRole(key0, "role2");
_dbClient.persistObject(obj);
}
verifyContain(constraint1, id, 1);
verifyContain(constraint0, null, 0);
}
@Test
public void testScopedLabelIndex() {
URI id = URIUtil.createId(Volume.class);
URI pid0 = URIUtil.createId(TenantOrg.class);
URI pid1 = URIUtil.createId(TenantOrg.class);
String lbl0 = "abcd1234";
String lbl1 = "efgh5678";
PrefixConstraint constraint0 = new LabelConstraintImpl(pid0, lbl0, TypeMap.getDoType(Volume.class).getColumnField("tags"));
PrefixConstraint constraint1 = new LabelConstraintImpl(pid1, lbl1, TypeMap.getDoType(Volume.class).getColumnField("tags"));
{
Volume obj = new Volume();
obj.setId(id);
obj.setTag(new ScopedLabelSet());
obj.getTag().add(new ScopedLabel(pid0.toString(), lbl0));
_dbClient.createObject(obj);
}
verifyContain(constraint0, id, 1);
verifyContain(constraint1, null, 0);
{
Volume obj = _dbClient.queryObject(Volume.class, id);
obj.getTag().add(new ScopedLabel(pid1.toString(), lbl1));
_dbClient.persistObject(obj);
}
verifyContain(constraint0, id, 1);
verifyContain(constraint1, id, 1);
{
Volume obj = _dbClient.queryObject(Volume.class, id);
obj.getTag().remove(new ScopedLabel(pid0.toString(), lbl0));
_dbClient.persistObject(obj);
}
verifyContain(constraint1, id, 1);
verifyContain(constraint0, null, 0);
}
}