/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.usergrid.persistence.graph.serialization; import java.util.Iterator; import java.util.UUID; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.apache.usergrid.persistence.core.guice.MigrationManagerRule; import org.apache.usergrid.persistence.core.scope.ApplicationScope; import org.apache.usergrid.persistence.core.util.IdGenerator; import org.apache.usergrid.persistence.graph.Edge; import org.apache.usergrid.persistence.model.entity.Id; import org.apache.usergrid.persistence.model.util.UUIDGenerator; import com.fasterxml.uuid.UUIDComparator; import com.google.inject.Inject; import com.netflix.astyanax.Keyspace; import com.netflix.astyanax.MutationBatch; import com.netflix.astyanax.connectionpool.exceptions.ConnectionException; import com.netflix.astyanax.connectionpool.exceptions.NotFoundException; import com.netflix.astyanax.model.Column; import com.netflix.astyanax.model.ColumnFamily; import com.netflix.astyanax.serializers.StringSerializer; import static org.apache.usergrid.persistence.graph.test.util.EdgeTestUtils.createEdge; import static org.apache.usergrid.persistence.core.util.IdGenerator.createId; import static org.apache.usergrid.persistence.graph.test.util.EdgeTestUtils.createSearchEdge; import static org.apache.usergrid.persistence.graph.test.util.EdgeTestUtils.createSearchIdType; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * Made abstract to allow subclasses to perform the wiring required for the functional testing. */ public abstract class EdgeMetadataSerializationTest { @Inject @Rule public MigrationManagerRule migrationManagerRule; @Inject protected Keyspace keyspace; protected ApplicationScope scope; protected EdgeMetadataSerialization serialization; @Before public void setup() { scope = mock( ApplicationScope.class ); Id orgId = mock( Id.class ); when( orgId.getType() ).thenReturn( "organization" ); when( orgId.getUuid() ).thenReturn( UUIDGenerator.newTimeUUID() ); when( scope.getApplication() ).thenReturn( orgId ); serialization = getSerializationImpl(); } /** * Test write and read edge types from source -> target */ @Test public void readTargetEdgeTypes() throws ConnectionException { final Edge edge1 = createEdge( "source", "edge", "target" ); final Id sourceId = edge1.getSourceNode(); final Edge edge2 = createEdge( sourceId, "edge", IdGenerator.createId( "target2" ) ); final Edge edge3 = createEdge( sourceId, "edge2", IdGenerator.createId( "target3" ) ); //set writing the edge serialization.writeEdge( scope, edge1 ).execute(); serialization.writeEdge( scope, edge2 ).execute(); serialization.writeEdge( scope, edge3 ).execute(); //now check we get both types back Iterator<String> edges = serialization.getEdgeTypesFromSource( scope, createSearchEdge( sourceId, null ) ); assertEquals( "edge", edges.next() ); assertEquals( "edge2", edges.next() ); assertFalse( edges.hasNext() ); //now check we can resume correctly with a "last" edges = serialization.getEdgeTypesFromSource( scope, createSearchEdge( sourceId, "edge" ) ); assertEquals( "edge2", edges.next() ); assertFalse( edges.hasNext() ); } /** * Test write and read edge types from source -> target */ @Test public void readSourceEdgeTypes() throws ConnectionException { final Edge edge1 = createEdge( "source", "edge", "target" ); final Id targetId = edge1.getTargetNode(); final Edge edge2 = createEdge( IdGenerator.createId( "source" ), "edge", targetId ); final Edge edge3 = createEdge( IdGenerator.createId( "source2" ), "edge2", targetId ); //set writing the edge serialization.writeEdge( scope, edge1 ).execute(); serialization.writeEdge( scope, edge2 ).execute(); serialization.writeEdge( scope, edge3 ).execute(); //now check we get both types back Iterator<String> edges = serialization.getEdgeTypesToTarget( scope, createSearchEdge( targetId, null ) ); assertEquals( "edge", edges.next() ); assertEquals( "edge2", edges.next() ); assertFalse( edges.hasNext() ); //now check we can resume correctly with a "last" edges = serialization.getEdgeTypesToTarget( scope, createSearchEdge( targetId, "edge" ) ); assertEquals( "edge2", edges.next() ); assertFalse( edges.hasNext() ); } /** * Test write and read edge types from source -> target */ @Test public void readTargetEdgeIdTypes() throws ConnectionException { final Edge edge1 = createEdge( "source", "edge", "target" ); final Id sourceId = edge1.getSourceNode(); final Edge edge2 = createEdge( sourceId, "edge", IdGenerator.createId( "target" ) ); final Edge edge3 = createEdge( sourceId, "edge", IdGenerator.createId( "target2" ) ); //shouldn't be returned final Edge edge4 = createEdge( sourceId, "edge2", IdGenerator.createId( "target3" ) ); //set writing the edge serialization.writeEdge( scope, edge1 ).execute(); serialization.writeEdge( scope, edge2 ).execute(); serialization.writeEdge( scope, edge3 ).execute(); serialization.writeEdge( scope, edge4 ).execute(); //now check we get both types back Iterator<String> types = serialization.getIdTypesFromSource( scope, createSearchIdType( sourceId, "edge", null ) ); assertEquals( "target", types.next() ); assertEquals( "target2", types.next() ); assertFalse( types.hasNext() ); //now check we can resume correctly with a "last" types = serialization.getIdTypesFromSource( scope, createSearchIdType( sourceId, "edge", "target" ) ); assertEquals( "target2", types.next() ); assertFalse( types.hasNext() ); //check by other type types = serialization.getIdTypesFromSource( scope, createSearchIdType( sourceId, "edge2", null ) ); assertEquals( "target3", types.next() ); assertFalse( types.hasNext() ); } /** * Test write and read edge types from source -> target */ @Test public void readSourceEdgeIdTypes() throws ConnectionException { final Edge edge1 = createEdge( "source", "edge", "target" ); final Id targetId = edge1.getTargetNode(); final Edge edge2 = createEdge( IdGenerator.createId( "source" ), "edge", targetId ); final Edge edge3 = createEdge( IdGenerator.createId( "source2" ), "edge", targetId ); //shouldn't be returned final Edge edge4 = createEdge( IdGenerator.createId( "source3" ), "edge2", targetId ); //set writing the edge serialization.writeEdge( scope, edge1 ).execute(); serialization.writeEdge( scope, edge2 ).execute(); serialization.writeEdge( scope, edge3 ).execute(); serialization.writeEdge( scope, edge4 ).execute(); //now check we get both types back Iterator<String> types = serialization.getIdTypesToTarget( scope, createSearchIdType( targetId, "edge", null ) ); assertEquals( "source", types.next() ); assertEquals( "source2", types.next() ); assertFalse( types.hasNext() ); //now check we can resume correctly with a "last" types = serialization.getIdTypesToTarget( scope, createSearchIdType( targetId, "edge", "source" ) ); assertEquals( "source2", types.next() ); assertFalse( types.hasNext() ); //check by other type types = serialization.getIdTypesToTarget( scope, createSearchIdType( targetId, "edge2", null ) ); assertEquals( "source3", types.next() ); assertFalse( types.hasNext() ); } /** * Test write and read edge types from source -> target */ @Test public void deleteTargetEdgeTypes() throws ConnectionException { final long timestamp = 1000l; final Edge edge1 = createEdge( "source", "edge", "target", timestamp ); final Id sourceId = edge1.getSourceNode(); final Edge edge2 = createEdge( sourceId, "edge", IdGenerator.createId( "target2" ), timestamp + 1 ); final Edge edge3 = createEdge( sourceId, "edge2", IdGenerator.createId( "target3" ), timestamp + 2 ); //set writing the edge serialization.writeEdge( scope, edge1 ).execute(); serialization.writeEdge( scope, edge2 ).execute(); serialization.writeEdge( scope, edge3 ).execute(); //now check we get both types back Iterator<String> edges = serialization.getEdgeTypesFromSource( scope, createSearchEdge( sourceId, null ) ); assertEquals( "edge", edges.next() ); assertEquals( "edge2", edges.next() ); assertFalse( edges.hasNext() ); //this shouldn't remove the edge, since edge1 has a version < edge2 serialization.removeEdgeTypeFromSource( scope, edge1 ).execute(); edges = serialization.getEdgeTypesFromSource( scope, createSearchEdge( sourceId, null ) ); assertEquals( "edge", edges.next() ); assertEquals( "edge2", edges.next() ); assertFalse( edges.hasNext() ); //this should delete it. The version is the max for that edge type serialization.removeEdgeTypeFromSource( scope, edge2 ).execute(); //now check we have 1 left edges = serialization.getEdgeTypesFromSource( scope, createSearchEdge( sourceId, null ) ); assertEquals( "edge2", edges.next() ); assertFalse( edges.hasNext() ); serialization.removeEdgeTypeFromSource( scope, edge3 ).execute(); //check we have nothing edges = serialization.getEdgeTypesFromSource( scope, createSearchEdge( sourceId, null ) ); assertFalse( edges.hasNext() ); } /** * Test write and read edge types from source -> target */ @Test public void deleteSourceEdgeTypes() throws ConnectionException { final Edge edge1 = createEdge( "source", "edge", "target" ); final Id targetId = edge1.getTargetNode(); final Edge edge2 = createEdge( IdGenerator.createId( "source" ), "edge", targetId ); final Edge edge3 = createEdge( IdGenerator.createId( "source2" ), "edge2", targetId ); //set writing the edge serialization.writeEdge( scope, edge1 ).execute(); serialization.writeEdge( scope, edge2 ).execute(); serialization.writeEdge( scope, edge3 ).execute(); //now check we get both types back Iterator<String> edges = serialization.getEdgeTypesToTarget( scope, createSearchEdge( targetId, null ) ); assertEquals( "edge", edges.next() ); assertEquals( "edge2", edges.next() ); assertFalse( edges.hasNext() ); //this shouldn't remove the edge, since edge1 has a version < edge2 serialization.removeEdgeTypeFromSource( scope, edge1 ).execute(); edges = serialization.getEdgeTypesToTarget( scope, createSearchEdge( targetId, null ) ); assertEquals( "edge", edges.next() ); assertEquals( "edge2", edges.next() ); assertFalse( edges.hasNext() ); serialization.removeEdgeTypeToTarget( scope, edge2 ).execute(); //now check we have 1 left edges = serialization.getEdgeTypesToTarget( scope, createSearchEdge( targetId, null ) ); assertEquals( "edge2", edges.next() ); assertFalse( edges.hasNext() ); serialization.removeEdgeTypeToTarget( scope, edge3 ).execute(); //check we have nothing edges = serialization.getEdgeTypesToTarget( scope, createSearchEdge( targetId, null ) ); assertFalse( edges.hasNext() ); } /** * Test write and read edge types from source -> target */ @Test public void deleteTargetIdTypes() throws ConnectionException { final long timestamp = 1000l; final Edge edge1 = createEdge( "source", "edge", "target", timestamp ); final Id sourceId = edge1.getSourceNode(); final Edge edge2 = createEdge( sourceId, "edge", IdGenerator.createId( "target" ), timestamp + 1 ); final Edge edge3 = createEdge( sourceId, "edge", IdGenerator.createId( "target2" ), timestamp + 2 ); //set writing the edge serialization.writeEdge( scope, edge1 ).execute(); serialization.writeEdge( scope, edge2 ).execute(); serialization.writeEdge( scope, edge3 ).execute(); //now check we get both types back Iterator<String> edges = serialization.getIdTypesFromSource( scope, createSearchIdType( sourceId, "edge", null ) ); assertEquals( "target", edges.next() ); assertEquals( "target2", edges.next() ); assertFalse( edges.hasNext() ); //this shouldn't remove the edge, since edge1 has a version < edge2 serialization.removeIdTypeFromSource( scope, edge1 ).execute(); edges = serialization.getIdTypesFromSource( scope, createSearchIdType( sourceId, "edge", null ) ); assertEquals( "target", edges.next() ); assertEquals( "target2", edges.next() ); assertFalse( edges.hasNext() ); //this should delete it. The version is the max for that edge type serialization.removeIdTypeFromSource( scope, edge2 ).execute(); //now check we have 1 left edges = serialization.getIdTypesFromSource( scope, createSearchIdType( sourceId, "edge", null ) ); assertEquals( "target2", edges.next() ); assertFalse( edges.hasNext() ); serialization.removeIdTypeFromSource( scope, edge3 ).execute(); //check we have nothing edges = serialization.getIdTypesFromSource( scope, createSearchIdType( sourceId, "edge", null ) ); assertFalse( edges.hasNext() ); } /** * Test write and read edge types from source -> target */ @Test public void deleteSourceIdTypes() throws ConnectionException { final long timestamp = 1000l; final Edge edge1 = createEdge( "source", "edge", "target", timestamp ); final Id targetId = edge1.getTargetNode(); final Edge edge2 = createEdge( IdGenerator.createId( "source" ), "edge", targetId, timestamp + 1 ); final Edge edge3 = createEdge( IdGenerator.createId( "source2" ), "edge", targetId, timestamp + 2 ); //set writing the edge serialization.writeEdge( scope, edge1 ).execute(); serialization.writeEdge( scope, edge2 ).execute(); serialization.writeEdge( scope, edge3 ).execute(); //now check we get both types back Iterator<String> edges = serialization.getIdTypesToTarget( scope, createSearchIdType( targetId, "edge", null ) ); assertEquals( "source", edges.next() ); assertEquals( "source2", edges.next() ); assertFalse( edges.hasNext() ); //this shouldn't remove the edge, since edge1 has a version < edge2 serialization.removeIdTypeToTarget( scope, edge1 ).execute(); edges = serialization.getIdTypesToTarget( scope, createSearchIdType( targetId, "edge", null ) ); assertEquals( "source", edges.next() ); assertEquals( "source2", edges.next() ); assertFalse( edges.hasNext() ); serialization.removeIdTypeToTarget( scope, edge2 ).execute(); //now check we have 1 left edges = serialization.getIdTypesToTarget( scope, createSearchIdType( targetId, "edge", null ) ); assertEquals( "source2", edges.next() ); assertFalse( edges.hasNext() ); serialization.removeIdTypeToTarget( scope, edge3 ).execute(); //check we have nothing edges = serialization.getIdTypesToTarget( scope, createSearchIdType( targetId, "edge", null ) ); assertFalse( edges.hasNext() ); } @Test public void validateDeleteCollision() throws ConnectionException { final String CF_NAME = "test"; final StringSerializer STR_SER = StringSerializer.get(); ColumnFamily<String, String> testCf = new ColumnFamily<String, String>( CF_NAME, STR_SER, STR_SER ); if ( keyspace.describeKeyspace().getColumnFamily( CF_NAME ) == null ) { keyspace.createColumnFamily( testCf, null ); } final String key = "key"; final String colname = "name"; final String colvalue = "value"; UUID firstUUID = UUIDGenerator.newTimeUUID(); UUID secondUUID = UUIDGenerator.newTimeUUID(); UUID thirdUUID = UUIDGenerator.newTimeUUID(); assertTrue( "First before second", UUIDComparator.staticCompare( firstUUID, secondUUID ) < 0 ); assertTrue( "Second before third", UUIDComparator.staticCompare( secondUUID, thirdUUID ) < 0 ); MutationBatch batch = keyspace.prepareMutationBatch(); batch.withRow( testCf, key ).setTimestamp( firstUUID.timestamp() ).putColumn( colname, colvalue ); batch.execute(); //now read it back to validate Column<String> col = keyspace.prepareQuery( testCf ).getKey( key ).getColumn( colname ).execute().getResult(); assertEquals( colname, col.getName() ); assertEquals( colvalue, col.getStringValue() ); //now issue a write and a delete with the same timestamp, write will win batch = keyspace.prepareMutationBatch(); batch.withRow( testCf, key ).setTimestamp( firstUUID.timestamp() ).putColumn( colname, colvalue ); batch.withRow( testCf, key ).setTimestamp( firstUUID.timestamp() ).deleteColumn( colname ); batch.execute(); boolean deleted = false; try { keyspace.prepareQuery( testCf ).getKey( key ).getColumn( colname ).execute().getResult(); } catch ( NotFoundException nfe ) { deleted = true; } assertTrue( deleted ); //ensure that if we have a latent write, it won't overwrite a newer value batch.withRow( testCf, key ).setTimestamp( secondUUID.timestamp() ).putColumn( colname, colvalue ); batch.execute(); col = keyspace.prepareQuery( testCf ).getKey( key ).getColumn( colname ).execute().getResult(); assertEquals( colname, col.getName() ); assertEquals( colvalue, col.getStringValue() ); //now issue a delete with the first timestamp, column should still be present batch = keyspace.prepareMutationBatch(); batch.withRow( testCf, key ).setTimestamp( firstUUID.timestamp() ).deleteColumn( colname ); batch.execute(); col = keyspace.prepareQuery( testCf ).getKey( key ).getColumn( colname ).execute().getResult(); assertEquals( colname, col.getName() ); assertEquals( colvalue, col.getStringValue() ); //now delete it with the 3rd timestamp, it should disappear batch = keyspace.prepareMutationBatch(); batch.withRow( testCf, key ).setTimestamp( thirdUUID.timestamp() ).deleteColumn( colname ); batch.execute(); deleted = false; try { keyspace.prepareQuery( testCf ).getKey( key ).getColumn( colname ).execute().getResult(); } catch ( NotFoundException nfe ) { deleted = true; } assertTrue( deleted ); } protected abstract EdgeMetadataSerialization getSerializationImpl(); }