From 32007956576730d3ec319bc3f97e6619c228a60d Mon Sep 17 00:00:00 2001 From: Mikhaylo Demianenko Date: Wed, 17 Aug 2016 13:21:44 +0300 Subject: [PATCH] Allow to load and use 3.0 high limit format (without fixed references part) as independent HighLimitV3_0 record format. Update high limit format store version to `vE.H.1`. --- .../kernel/impl/store/format/Capability.java | 5 + .../impl/store/format/StoreVersion.java | 3 +- .../participant/StoreMigrator.java | 3 +- .../ConcurrentChangesOnEntitiesTest.java | 9 +- .../test/java/upgrade/StoreUpgraderTest.java | 49 ++++++--- .../store/format/highlimit/HighLimit.java | 6 +- .../format/highlimit/v30/HighLimitV3_0.java | 3 +- .../highlimit/v30/HighLimitV3_0Factory.java | 38 +++++++ ...el.impl.store.format.RecordFormats$Factory | 3 +- .../format/RecordFormatSelectorTest.java | 3 + .../HighLimitStoreMigrationTest.java | 101 ++++++++++++++++++ .../v30/HighLimitV3_0RecordFormatTest.java | 30 ++++++ .../neo4j/RecordFormatsGenerationTest.java | 2 + .../upgrade/EnterpriseStoreUpgraderTest.java | 88 +++++++++++++++ .../upgrade/RecordFormatsMigrationIT.java | 27 +++-- ...tandardToEnterpriseStoreUpgraderTest.java} | 8 +- .../neo4j}/upgrade/StoreMigratorFrom20IT.java | 6 +- .../neo4j}/upgrade/StoreMigratorFrom21IT.java | 2 +- .../neo4j}/upgrade/StoreMigratorTestUtil.java | 14 +-- .../upgrade/upgradeTest30HighLimitDb.zip | Bin 0 -> 28097 bytes 20 files changed, 357 insertions(+), 43 deletions(-) create mode 100644 enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/v30/HighLimitV3_0Factory.java create mode 100644 enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/highlimit/HighLimitStoreMigrationTest.java create mode 100644 enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/highlimit/v30/HighLimitV3_0RecordFormatTest.java create mode 100644 enterprise/neo4j-enterprise/src/test/java/org/neo4j/upgrade/EnterpriseStoreUpgraderTest.java rename enterprise/neo4j-enterprise/src/test/java/{ => org/neo4j}/upgrade/RecordFormatsMigrationIT.java (86%) rename enterprise/neo4j-enterprise/src/test/java/{upgrade/EnterpriseStoreUpgraderTest.java => org/neo4j/upgrade/StandardToEnterpriseStoreUpgraderTest.java} (86%) rename enterprise/neo4j-enterprise/src/test/java/{ => org/neo4j}/upgrade/StoreMigratorFrom20IT.java (98%) rename enterprise/neo4j-enterprise/src/test/java/{ => org/neo4j}/upgrade/StoreMigratorFrom21IT.java (99%) rename enterprise/neo4j-enterprise/src/test/java/{ => org/neo4j}/upgrade/StoreMigratorTestUtil.java (79%) create mode 100644 enterprise/neo4j-enterprise/src/test/resources/org/neo4j/upgrade/upgradeTest30HighLimitDb.zip diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/Capability.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/Capability.java index 4af806f61b918..0cad47a4a2cde 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/Capability.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/Capability.java @@ -37,6 +37,11 @@ public enum Capability */ DENSE_NODES( CapabilityType.FORMAT, CapabilityType.STORE ), + /** + * Store has fixed reference encoding support support + */ + FIXED_REFERENCE( CapabilityType.FORMAT ), + /** * Store has version trailers in the end of cleanly shut down store */ diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/StoreVersion.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/StoreVersion.java index d95674fe9f6d4..bb60c2066fbd4 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/StoreVersion.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/StoreVersion.java @@ -32,7 +32,8 @@ public enum StoreVersion STANDARD_V2_3( "v0.A.6", true ), STANDARD_V3_0( "v0.A.7", true ), - HIGH_LIMIT_V3_0( "vE.H.0", false ); + HIGH_LIMIT_V3_0( "vE.H.0", false ), + HIGH_LIMIT_V3_1( "vE.H.1", false ); private static final StoreVersion[] ALL_STORE_VERSIONS = values(); diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/storemigration/participant/StoreMigrator.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/storemigration/participant/StoreMigrator.java index 6b33e91994e7b..6318c3b3b5ab5 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/storemigration/participant/StoreMigrator.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/storemigration/participant/StoreMigrator.java @@ -55,6 +55,7 @@ import org.neo4j.kernel.impl.store.StoreType; import org.neo4j.kernel.impl.store.TransactionId; import org.neo4j.kernel.impl.store.counts.CountsTracker; +import org.neo4j.kernel.impl.store.format.CapabilityType; import org.neo4j.kernel.impl.store.format.RecordFormats; import org.neo4j.kernel.impl.store.format.standard.MetaDataRecordFormat; import org.neo4j.kernel.impl.store.format.standard.NodeRecordFormat; @@ -169,7 +170,7 @@ public void migrate( File storeDir, File migrationDir, MigrationProgressMonitor. RecordFormats oldFormat = selectForVersion( versionToMigrateFrom ); RecordFormats newFormat = selectForVersion( versionToMigrateTo ); - if ( !oldFormat.equals( newFormat ) ) + if ( !oldFormat.hasSameCapabilities( newFormat, CapabilityType.FORMAT ) ) { // TODO if this store has relationship indexes then warn user about that they will be incorrect // after migration, because now we're rewriting the relationship ids. diff --git a/community/neo4j/src/test/java/synchronization/ConcurrentChangesOnEntitiesTest.java b/community/neo4j/src/test/java/synchronization/ConcurrentChangesOnEntitiesTest.java index 26be44a8e409c..c5bcb6a078ced 100644 --- a/community/neo4j/src/test/java/synchronization/ConcurrentChangesOnEntitiesTest.java +++ b/community/neo4j/src/test/java/synchronization/ConcurrentChangesOnEntitiesTest.java @@ -22,6 +22,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.RuleChain; import java.io.IOException; import java.util.concurrent.CyclicBarrier; @@ -41,6 +42,7 @@ import org.neo4j.kernel.configuration.Config; import org.neo4j.logging.FormattedLogProvider; import org.neo4j.logging.LogProvider; +import org.neo4j.test.rule.SuppressOutput; import org.neo4j.test.rule.TestDirectory; import static org.junit.Assert.assertTrue; @@ -48,8 +50,13 @@ public class ConcurrentChangesOnEntitiesTest { + + private SuppressOutput suppressOutput = SuppressOutput.suppressAll(); + private TestDirectory testDirectory = TestDirectory.testDirectory(); + @Rule - public TestDirectory testDirectory = TestDirectory.testDirectory(); + public RuleChain ruleChain = RuleChain.outerRule( suppressOutput ).around( testDirectory ); + private final CyclicBarrier barrier = new CyclicBarrier( 2 ); private final AtomicReference ex = new AtomicReference<>(); private GraphDatabaseService db; diff --git a/community/neo4j/src/test/java/upgrade/StoreUpgraderTest.java b/community/neo4j/src/test/java/upgrade/StoreUpgraderTest.java index 4bb9da16becd5..838208d42ad2d 100644 --- a/community/neo4j/src/test/java/upgrade/StoreUpgraderTest.java +++ b/community/neo4j/src/test/java/upgrade/StoreUpgraderTest.java @@ -32,6 +32,9 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.neo4j.consistency.checking.full.ConsistencyCheckIncompleteException; import org.neo4j.graphdb.GraphDatabaseService; @@ -85,6 +88,7 @@ import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.verify; @@ -139,7 +143,7 @@ public void prepareDb() throws IOException { dbDirectory = directory.directory( "db_" + version ); File prepareDirectory = directory.directory( "prepare_" + version ); - prepareSampleLegacyDatabase( version, fileSystem, dbDirectory, prepareDirectory ); + prepareSampleDatabase( version, fileSystem, dbDirectory, prepareDirectory ); } @Test @@ -150,7 +154,11 @@ public void shouldUpgradeAnOldFormatStore() throws IOException, ConsistencyCheck UpgradableDatabase upgradableDatabase = new UpgradableDatabase( fileSystem, new StoreVersionCheck( pageCache ), new LegacyStoreVersionCheck( fileSystem ), getRecordFormats() ); - assertEquals( !StandardV2_3.STORE_VERSION.equals( version ), + Set versionSet = versionSet( StandardV2_0.STORE_VERSION, + StandardV2_1.STORE_VERSION, + StandardV2_2.STORE_VERSION ); + + assertEquals( versionSet.contains( version ), allLegacyStoreFilesHaveVersion( fileSystem, dbDirectory, version ) ); // When @@ -194,7 +202,9 @@ public void shouldHaltUpgradeIfUpgradeConfigurationVetoesTheProcess() public void shouldLeaveAllFilesUntouchedIfWrongVersionNumberFound() throws IOException { - Assume.assumeFalse( StandardV2_3.STORE_VERSION.equals( version ) ); + Set applicableVersions = + versionSet( StandardV2_0.STORE_VERSION, StandardV2_1.STORE_VERSION, StandardV2_2.STORE_VERSION ); + assumeTrue( "Applicable only to specified version set.", applicableVersions.contains( version ) ); File comparisonDirectory = new File( "target/" + StoreUpgraderTest.class.getSimpleName() + "shouldLeaveAllFilesUntouchedIfWrongVersionNumberFound-comparison" ); @@ -221,11 +231,11 @@ public void shouldLeaveAllFilesUntouchedIfWrongVersionNumberFound() } @Test - public void shouldRefuseToUpgradeIfAnyOfTheStoresWeNotShutDownCleanly() + public void shouldRefuseToUpgradeIfAnyOfTheStoresWereNotShutDownCleanly() throws IOException { - File comparisonDirectory = new File( "target/" + StoreUpgraderTest.class.getSimpleName() - + "shouldRefuseToUpgradeIfAnyOfTheStoresWeNotShutDownCleanly-comparison" ); + File comparisonDirectory = new File( "target/" + StoreUpgraderTest.class.getSimpleName() + + "shouldRefuseToUpgradeIfAnyOfTheStoresWereNotShutDownCleanly-comparison" ); makeDbNotCleanlyShutdown( false ); fileSystem.deleteRecursively( comparisonDirectory ); fileSystem.copyRecursively( dbDirectory, comparisonDirectory ); @@ -251,8 +261,8 @@ public void shouldRefuseToUpgradeIfAnyOfTheStoresWeNotShutDownCleanly() public void shouldRefuseToUpgradeIfAllOfTheStoresWereNotShutDownCleanly() throws IOException { - File comparisonDirectory = new File( "target/" + StoreUpgraderTest.class.getSimpleName() - + "shouldRefuseToUpgradeIfAllOfTheStoresWeNotShutDownCleanly-comparison" ); + File comparisonDirectory = new File( "target/" + StoreUpgraderTest.class.getSimpleName() + + "shouldRefuseToUpgradeIfAllOfTheStoresWereNotShutDownCleanly-comparison" ); makeDbNotCleanlyShutdown( true ); fileSystem.deleteRecursively( comparisonDirectory ); fileSystem.copyRecursively( dbDirectory, comparisonDirectory ); @@ -390,6 +400,12 @@ public void upgraderShouldCleanupLegacyLeftoverAndMigrationDirs() throws Excepti assertThat( migrationHelperDirs(), is( emptyCollectionOf( File.class ) ) ); } + protected void prepareSampleDatabase( String version, FileSystemAbstraction fileSystem, File dbDirectory, + File databaseDirectory ) throws IOException + { + prepareSampleLegacyDatabase( version, fileSystem, dbDirectory, databaseDirectory ); + } + private static void assertCorrectStoreVersion( String expectedStoreVersion, StoreVersionCheck check, File storeDir ) { File neoStoreFile = new File( storeDir, MetaDataStore.DEFAULT_NAME ); @@ -449,11 +465,9 @@ private List migrationHelperDirs() private void makeDbNotCleanlyShutdown( boolean truncateAll ) throws IOException { - if ( StandardV2_3.STORE_VERSION.equals( version ) ) - { - removeCheckPointFromTxLog( fileSystem, dbDirectory ); - } - else + Set truncateVersions = versionSet( StandardV2_0.STORE_VERSION, StandardV2_1.STORE_VERSION, + StandardV2_2.STORE_VERSION ); + if (truncateVersions.contains( version )) { if ( truncateAll ) { @@ -466,6 +480,10 @@ private void makeDbNotCleanlyShutdown( boolean truncateAll ) throws IOException truncateFile( fileSystem, storeFile, "StringPropertyStore " + version ); } } + else + { + removeCheckPointFromTxLog( fileSystem, dbDirectory ); + } } private Config getTuningConfig() @@ -483,6 +501,11 @@ protected String getRecordFormatsName() return StandardV3_0.NAME; } + private Set versionSet(String... versions) + { + return Stream.of( versions ).collect( Collectors.toSet() ); + } + private void startStopDatabase() { GraphDatabaseService databaseService = new TestGraphDatabaseFactory().newEmbeddedDatabase( dbDirectory ); diff --git a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/HighLimit.java b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/HighLimit.java index b2c48157c5256..ea56046074472 100644 --- a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/HighLimit.java +++ b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/HighLimit.java @@ -24,6 +24,7 @@ import org.neo4j.kernel.impl.store.format.RecordFormat; import org.neo4j.kernel.impl.store.format.RecordFormats; import org.neo4j.kernel.impl.store.format.StoreVersion; +import org.neo4j.kernel.impl.store.format.highlimit.v30.HighLimitV3_0; import org.neo4j.kernel.impl.store.format.standard.LabelTokenRecordFormat; import org.neo4j.kernel.impl.store.format.standard.PropertyKeyTokenRecordFormat; import org.neo4j.kernel.impl.store.format.standard.RelationshipTypeTokenRecordFormat; @@ -48,13 +49,14 @@ public class HighLimit extends BaseRecordFormats */ static final int DEFAULT_MAXIMUM_BITS_PER_ID = 50; - public static final String STORE_VERSION = StoreVersion.HIGH_LIMIT_V3_0.versionString(); + public static final String STORE_VERSION = StoreVersion.HIGH_LIMIT_V3_1.versionString(); public static final RecordFormats RECORD_FORMATS = new HighLimit(); public static final String NAME = "high_limit"; public HighLimit() { - super( STORE_VERSION, 8, Capability.DENSE_NODES, Capability.SCHEMA, Capability.LUCENE_5 ); + super( STORE_VERSION, 8, Capability.DENSE_NODES, Capability.SCHEMA, Capability.LUCENE_5, + Capability.FIXED_REFERENCE ); } @Override diff --git a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/v30/HighLimitV3_0.java b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/v30/HighLimitV3_0.java index b943763b4c1d8..c24139a74dcff 100644 --- a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/v30/HighLimitV3_0.java +++ b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/v30/HighLimitV3_0.java @@ -55,7 +55,8 @@ public class HighLimitV3_0 extends BaseRecordFormats public HighLimitV3_0() { - super( STORE_VERSION, 7, Capability.DENSE_NODES, Capability.SCHEMA, Capability.LUCENE_5 ); + super( STORE_VERSION, 7, Capability.DENSE_NODES, Capability.SCHEMA, Capability.LUCENE_5, + Capability.FIXED_REFERENCE ); } @Override diff --git a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/v30/HighLimitV3_0Factory.java b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/v30/HighLimitV3_0Factory.java new file mode 100644 index 0000000000000..cbcfaee756bfb --- /dev/null +++ b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/v30/HighLimitV3_0Factory.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.neo4j.kernel.impl.store.format.highlimit.v30; + +import org.neo4j.helpers.Service; +import org.neo4j.kernel.impl.store.format.RecordFormats; + +@Service.Implementation( RecordFormats.Factory.class ) +public class HighLimitV3_0Factory extends RecordFormats.Factory +{ + public HighLimitV3_0Factory() + { + super( HighLimitV3_0.NAME ); + } + + @Override + public RecordFormats newInstance() + { + return HighLimitV3_0.RECORD_FORMATS; + } +} diff --git a/enterprise/kernel/src/main/resources/META-INF/services/org.neo4j.kernel.impl.store.format.RecordFormats$Factory b/enterprise/kernel/src/main/resources/META-INF/services/org.neo4j.kernel.impl.store.format.RecordFormats$Factory index 34e1474a78ff6..55c936755dd29 100644 --- a/enterprise/kernel/src/main/resources/META-INF/services/org.neo4j.kernel.impl.store.format.RecordFormats$Factory +++ b/enterprise/kernel/src/main/resources/META-INF/services/org.neo4j.kernel.impl.store.format.RecordFormats$Factory @@ -1 +1,2 @@ -org.neo4j.kernel.impl.store.format.highlimit.HighLimitFactory \ No newline at end of file +org.neo4j.kernel.impl.store.format.highlimit.HighLimitFactory +org.neo4j.kernel.impl.store.format.highlimit.v30.HighLimitV3_0Factory diff --git a/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/RecordFormatSelectorTest.java b/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/RecordFormatSelectorTest.java index 2b7d93da418e7..c170a9b697843 100644 --- a/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/RecordFormatSelectorTest.java +++ b/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/RecordFormatSelectorTest.java @@ -32,6 +32,7 @@ import org.neo4j.kernel.configuration.Config; import org.neo4j.kernel.impl.store.MetaDataStore; import org.neo4j.kernel.impl.store.format.highlimit.HighLimit; +import org.neo4j.kernel.impl.store.format.highlimit.v30.HighLimitV3_0; import org.neo4j.kernel.impl.store.format.standard.StandardV2_0; import org.neo4j.kernel.impl.store.format.standard.StandardV2_1; import org.neo4j.kernel.impl.store.format.standard.StandardV2_2; @@ -83,6 +84,7 @@ public void selectForVersionTest() assertSame( StandardV2_2.RECORD_FORMATS, selectForVersion( StandardV2_2.STORE_VERSION ) ); assertSame( StandardV2_3.RECORD_FORMATS, selectForVersion( StandardV2_3.STORE_VERSION ) ); assertSame( StandardV3_0.RECORD_FORMATS, selectForVersion( StandardV3_0.STORE_VERSION ) ); + assertSame( HighLimitV3_0.RECORD_FORMATS, selectForVersion( HighLimitV3_0.STORE_VERSION ) ); assertSame( HighLimit.RECORD_FORMATS, selectForVersion( HighLimit.STORE_VERSION ) ); } @@ -136,6 +138,7 @@ public void selectForStoreWithValidStore() throws IOException verifySelectForStore( pageCache, StandardV2_2.RECORD_FORMATS ); verifySelectForStore( pageCache, StandardV2_3.RECORD_FORMATS ); verifySelectForStore( pageCache, StandardV3_0.RECORD_FORMATS ); + verifySelectForStore( pageCache, HighLimitV3_0.RECORD_FORMATS ); verifySelectForStore( pageCache, HighLimit.RECORD_FORMATS ); } diff --git a/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/highlimit/HighLimitStoreMigrationTest.java b/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/highlimit/HighLimitStoreMigrationTest.java new file mode 100644 index 0000000000000..332adc68a1174 --- /dev/null +++ b/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/highlimit/HighLimitStoreMigrationTest.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.neo4j.kernel.impl.store.format.highlimit; + +import org.hamcrest.CoreMatchers; +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.neo4j.graphdb.mockfs.EphemeralFileSystemAbstraction; +import org.neo4j.io.fs.FileSystemAbstraction; +import org.neo4j.io.pagecache.PageCache; +import org.neo4j.kernel.api.index.SchemaIndexProvider; +import org.neo4j.kernel.configuration.Config; +import org.neo4j.kernel.impl.logging.NullLogService; +import org.neo4j.kernel.impl.store.MetaDataStore; +import org.neo4j.kernel.impl.store.format.CapabilityType; +import org.neo4j.kernel.impl.store.format.highlimit.v30.HighLimitV3_0; +import org.neo4j.kernel.impl.storemigration.monitoring.MigrationProgressMonitor; +import org.neo4j.kernel.impl.storemigration.participant.StoreMigrator; +import org.neo4j.test.rule.PageCacheRule; + +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.neo4j.kernel.impl.store.MetaDataStore.Position.STORE_VERSION; + +public class HighLimitStoreMigrationTest +{ + @Rule + public final PageCacheRule pageCacheRule = new PageCacheRule(); + private final FileSystemAbstraction fileSystem = new EphemeralFileSystemAbstraction(); + + @Test + public void haveSameFormatCapabilitiesAsHighLimit3_0() + { + HighLimit.RECORD_FORMATS.hasSameCapabilities( HighLimitV3_0.RECORD_FORMATS, CapabilityType.FORMAT ); + } + + @Test + public void doNotMigrateHighLimit3_0StoreFiles() throws IOException + { + PageCache pageCache = pageCacheRule.getPageCache( fileSystem ); + SchemaIndexProvider schemaIndexProvider = mock( SchemaIndexProvider.class ); + StoreMigrator migrator = new StoreMigrator( fileSystem, pageCache, Config.empty(), NullLogService.getInstance(), + schemaIndexProvider ); + + File storeDir = new File( "storeDir" ); + File migrationDir = new File( "migrationDir" ); + fileSystem.mkdir( migrationDir ); + + prepareNeoStoreFile( storeDir, HighLimitV3_0.STORE_VERSION, pageCache ); + + MigrationProgressMonitor.Section progressMonitor = mock( MigrationProgressMonitor.Section.class ); + migrator.migrate( storeDir, migrationDir, progressMonitor, HighLimitV3_0.STORE_VERSION, HighLimit.STORE_VERSION ); + + File[] migrationFiles = fileSystem.listFiles( migrationDir ); + Set fileNames = Stream.of( migrationFiles ).map( File::getName ).collect( Collectors.toSet() ); + assertThat( "Only specified files should be created after migration attempt from 3.0 to 3.1 using high limit " + + "format. Since formats are compatible and migration is not required.", fileNames, + CoreMatchers.hasItems( "lastxinformation", "lastxlogposition" ) ); + } + + private File prepareNeoStoreFile( File storeDir, String storeVersion, PageCache pageCache ) throws IOException + { + File neoStoreFile = createNeoStoreFile( storeDir ); + long value = MetaDataStore.versionStringToLong( storeVersion ); + MetaDataStore.setRecord( pageCache, neoStoreFile, STORE_VERSION, value ); + return neoStoreFile; + } + + private File createNeoStoreFile( File storeDir ) throws IOException + { + fileSystem.mkdir( storeDir ); + File neoStoreFile = new File( storeDir, MetaDataStore.DEFAULT_NAME ); + fileSystem.create( neoStoreFile ).close(); + return neoStoreFile; + } + +} diff --git a/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/highlimit/v30/HighLimitV3_0RecordFormatTest.java b/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/highlimit/v30/HighLimitV3_0RecordFormatTest.java new file mode 100644 index 0000000000000..af783c528c66d --- /dev/null +++ b/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/highlimit/v30/HighLimitV3_0RecordFormatTest.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.neo4j.kernel.impl.store.format.highlimit.v30; + +import org.neo4j.kernel.impl.store.format.RecordFormatTest; + +public class HighLimitV3_0RecordFormatTest extends RecordFormatTest +{ + public HighLimitV3_0RecordFormatTest() + { + super( HighLimitV3_0.RECORD_FORMATS, 50, 50 ); + } +} diff --git a/enterprise/neo4j-enterprise/src/test/java/org/neo4j/RecordFormatsGenerationTest.java b/enterprise/neo4j-enterprise/src/test/java/org/neo4j/RecordFormatsGenerationTest.java index 3cdd270dabb08..f335e3ac71e2e 100644 --- a/enterprise/neo4j-enterprise/src/test/java/org/neo4j/RecordFormatsGenerationTest.java +++ b/enterprise/neo4j-enterprise/src/test/java/org/neo4j/RecordFormatsGenerationTest.java @@ -28,6 +28,7 @@ import org.neo4j.kernel.impl.store.format.RecordFormats; import org.neo4j.kernel.impl.store.format.StoreVersion; import org.neo4j.kernel.impl.store.format.highlimit.HighLimit; +import org.neo4j.kernel.impl.store.format.highlimit.v30.HighLimitV3_0; import org.neo4j.kernel.impl.store.format.standard.StandardV2_0; import org.neo4j.kernel.impl.store.format.standard.StandardV2_1; import org.neo4j.kernel.impl.store.format.standard.StandardV2_2; @@ -48,6 +49,7 @@ public void correctGenerations() StandardV2_2.RECORD_FORMATS.generation(), StandardV2_3.RECORD_FORMATS.generation(), StandardV3_0.RECORD_FORMATS.generation(), + HighLimitV3_0.RECORD_FORMATS.generation(), HighLimit.RECORD_FORMATS.generation() ); diff --git a/enterprise/neo4j-enterprise/src/test/java/org/neo4j/upgrade/EnterpriseStoreUpgraderTest.java b/enterprise/neo4j-enterprise/src/test/java/org/neo4j/upgrade/EnterpriseStoreUpgraderTest.java new file mode 100644 index 0000000000000..5874ae8a9de8b --- /dev/null +++ b/enterprise/neo4j-enterprise/src/test/java/org/neo4j/upgrade/EnterpriseStoreUpgraderTest.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.neo4j.upgrade; + +import org.junit.runners.Parameterized; +import upgrade.StoreUpgraderTest; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; + +import org.neo4j.io.fs.FileSystemAbstraction; +import org.neo4j.kernel.impl.store.format.RecordFormats; +import org.neo4j.kernel.impl.store.format.highlimit.HighLimit; +import org.neo4j.kernel.impl.store.format.highlimit.v30.HighLimitV3_0; +import org.neo4j.test.Unzip; + +import static java.util.Collections.singletonList; + +public class EnterpriseStoreUpgraderTest extends StoreUpgraderTest +{ + public EnterpriseStoreUpgraderTest( String version ) + { + super( version ); + } + + @Parameterized.Parameters( name = "{0}" ) + public static Collection versions() + { + return singletonList( HighLimitV3_0.STORE_VERSION ); + } + + @Override + protected RecordFormats getRecordFormats() + { + return HighLimit.RECORD_FORMATS; + } + + @Override + protected String getRecordFormatsName() + { + return HighLimit.NAME; + } + + @Override + protected void prepareSampleDatabase( String version, FileSystemAbstraction fileSystem, File dbDirectory, + File databaseDirectory ) throws IOException + { + File resourceDirectory = findFormatStoreDirectoryForVersion( version, databaseDirectory ); + fileSystem.deleteRecursively( dbDirectory ); + fileSystem.mkdirs( dbDirectory ); + fileSystem.copyRecursively( resourceDirectory, dbDirectory ); + } + + private File findFormatStoreDirectoryForVersion( String version, File databaseDirectory ) throws IOException + { + if ( version.equals( HighLimitV3_0.STORE_VERSION ) ) + { + return highLimit3_0Store( databaseDirectory ); + } + else + { + throw new IllegalArgumentException( "Unknown enterprise store version." ); + } + } + + private File highLimit3_0Store( File databaseDirectory ) throws IOException + { + return Unzip.unzip( EnterpriseStoreUpgraderTest.class, "upgradeTest30HighLimitDb.zip", databaseDirectory ); + } +} diff --git a/enterprise/neo4j-enterprise/src/test/java/upgrade/RecordFormatsMigrationIT.java b/enterprise/neo4j-enterprise/src/test/java/org/neo4j/upgrade/RecordFormatsMigrationIT.java similarity index 86% rename from enterprise/neo4j-enterprise/src/test/java/upgrade/RecordFormatsMigrationIT.java rename to enterprise/neo4j-enterprise/src/test/java/org/neo4j/upgrade/RecordFormatsMigrationIT.java index d99a27baa9d5c..8c7a74ad5156f 100644 --- a/enterprise/neo4j-enterprise/src/test/java/upgrade/RecordFormatsMigrationIT.java +++ b/enterprise/neo4j-enterprise/src/test/java/org/neo4j/upgrade/RecordFormatsMigrationIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package upgrade; +package org.neo4j.upgrade; import org.junit.Rule; import org.junit.Test; @@ -41,6 +41,7 @@ import org.neo4j.kernel.impl.store.format.RecordFormatSelector; import org.neo4j.kernel.impl.store.format.RecordFormats; import org.neo4j.kernel.impl.store.format.highlimit.HighLimit; +import org.neo4j.kernel.impl.store.format.highlimit.v30.HighLimitV3_0; import org.neo4j.kernel.impl.store.format.standard.StandardV3_0; import org.neo4j.kernel.impl.storemigration.StoreUpgrader.UnexpectedUpgradingStoreFormatException; import org.neo4j.logging.NullLogProvider; @@ -65,20 +66,30 @@ public class RecordFormatsMigrationIT public final TestDirectory testDir = TestDirectory.testDirectory( fs ); @Test - public void migrateStandardToHighLimit() throws IOException + public void migrateLatestStandardToLatestHighLimit() throws IOException { executeAndStopDb( startStandardFormatDb(), this::createNode ); - assertStandardStore(); + assertLatestStandardStore(); executeAndStopDb( startHighLimitFormatDb(), this::assertNodeExists ); - assertHighLimitStore(); + assertLatestHighLimitStore(); + } + + @Test + public void migrateHighLimitV3_0ToLatestHighLimit() throws IOException + { + executeAndStopDb( startDb( HighLimitV3_0.NAME ), this::createNode ); + assertStoreFormat( HighLimitV3_0.RECORD_FORMATS ); + + executeAndStopDb( startHighLimitFormatDb(), this::assertNodeExists ); + assertLatestHighLimitStore(); } @Test public void migrateHighLimitToStandard() throws IOException { executeAndStopDb( startHighLimitFormatDb(), this::createNode ); - assertHighLimitStore(); + assertLatestHighLimitStore(); try { @@ -89,7 +100,7 @@ public void migrateHighLimitToStandard() throws IOException { assertThat( Exceptions.rootCause( e ), instanceOf( UnexpectedUpgradingStoreFormatException.class ) ); } - assertHighLimitStore(); + assertLatestHighLimitStore(); } private void createNode( GraphDatabaseService db ) @@ -129,12 +140,12 @@ private GraphDatabaseService startDb( String recordFormatName ) .newGraphDatabase(); } - private void assertStandardStore() throws IOException + private void assertLatestStandardStore() throws IOException { assertStoreFormat( StandardV3_0.RECORD_FORMATS ); } - private void assertHighLimitStore() throws IOException + private void assertLatestHighLimitStore() throws IOException { assertStoreFormat( HighLimit.RECORD_FORMATS ); } diff --git a/enterprise/neo4j-enterprise/src/test/java/upgrade/EnterpriseStoreUpgraderTest.java b/enterprise/neo4j-enterprise/src/test/java/org/neo4j/upgrade/StandardToEnterpriseStoreUpgraderTest.java similarity index 86% rename from enterprise/neo4j-enterprise/src/test/java/upgrade/EnterpriseStoreUpgraderTest.java rename to enterprise/neo4j-enterprise/src/test/java/org/neo4j/upgrade/StandardToEnterpriseStoreUpgraderTest.java index ba7f1e7e38f88..b957aef9ab6d7 100644 --- a/enterprise/neo4j-enterprise/src/test/java/upgrade/EnterpriseStoreUpgraderTest.java +++ b/enterprise/neo4j-enterprise/src/test/java/org/neo4j/upgrade/StandardToEnterpriseStoreUpgraderTest.java @@ -17,7 +17,9 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package upgrade; +package org.neo4j.upgrade; + +import upgrade.StoreUpgraderTest; import org.neo4j.kernel.impl.store.format.RecordFormats; import org.neo4j.kernel.impl.store.format.highlimit.HighLimit; @@ -25,9 +27,9 @@ /** * Runs the store upgrader tests from older versions, migrating to the current enterprise version. */ -public class EnterpriseStoreUpgraderTest extends StoreUpgraderTest +public class StandardToEnterpriseStoreUpgraderTest extends StoreUpgraderTest { - public EnterpriseStoreUpgraderTest( String version ) + public StandardToEnterpriseStoreUpgraderTest( String version ) { super( version ); } diff --git a/enterprise/neo4j-enterprise/src/test/java/upgrade/StoreMigratorFrom20IT.java b/enterprise/neo4j-enterprise/src/test/java/org/neo4j/upgrade/StoreMigratorFrom20IT.java similarity index 98% rename from enterprise/neo4j-enterprise/src/test/java/upgrade/StoreMigratorFrom20IT.java rename to enterprise/neo4j-enterprise/src/test/java/org/neo4j/upgrade/StoreMigratorFrom20IT.java index 8e15617d3ee41..3f1ecf07febe4 100644 --- a/enterprise/neo4j-enterprise/src/test/java/upgrade/StoreMigratorFrom20IT.java +++ b/enterprise/neo4j-enterprise/src/test/java/org/neo4j/upgrade/StoreMigratorFrom20IT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package upgrade; +package org.neo4j.upgrade; import org.junit.After; import org.junit.Before; @@ -27,6 +27,8 @@ import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; +import upgrade.DatabaseContentVerifier; +import upgrade.ListAccumulatorMigrationProgressMonitor; import java.io.File; import java.io.IOException; @@ -76,7 +78,7 @@ import static org.neo4j.helpers.collection.MapUtil.stringMap; import static org.neo4j.kernel.impl.ha.ClusterManager.allSeesAllAsAvailable; import static org.neo4j.kernel.impl.storemigration.MigrationTestUtils.find20FormatStoreDirectory; -import static upgrade.StoreMigratorTestUtil.buildClusterWithMasterDirIn; +import static org.neo4j.upgrade.StoreMigratorTestUtil.buildClusterWithMasterDirIn; @RunWith( Parameterized.class ) public class StoreMigratorFrom20IT diff --git a/enterprise/neo4j-enterprise/src/test/java/upgrade/StoreMigratorFrom21IT.java b/enterprise/neo4j-enterprise/src/test/java/org/neo4j/upgrade/StoreMigratorFrom21IT.java similarity index 99% rename from enterprise/neo4j-enterprise/src/test/java/upgrade/StoreMigratorFrom21IT.java rename to enterprise/neo4j-enterprise/src/test/java/org/neo4j/upgrade/StoreMigratorFrom21IT.java index 6e5f90b092e2e..df092ef1f1a96 100644 --- a/enterprise/neo4j-enterprise/src/test/java/upgrade/StoreMigratorFrom21IT.java +++ b/enterprise/neo4j-enterprise/src/test/java/org/neo4j/upgrade/StoreMigratorFrom21IT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package upgrade; +package org.neo4j.upgrade; import org.junit.Rule; import org.junit.Test; diff --git a/enterprise/neo4j-enterprise/src/test/java/upgrade/StoreMigratorTestUtil.java b/enterprise/neo4j-enterprise/src/test/java/org/neo4j/upgrade/StoreMigratorTestUtil.java similarity index 79% rename from enterprise/neo4j-enterprise/src/test/java/upgrade/StoreMigratorTestUtil.java rename to enterprise/neo4j-enterprise/src/test/java/org/neo4j/upgrade/StoreMigratorTestUtil.java index 2e915b399701c..7a52298b3c1d5 100644 --- a/enterprise/neo4j-enterprise/src/test/java/upgrade/StoreMigratorTestUtil.java +++ b/enterprise/neo4j-enterprise/src/test/java/org/neo4j/upgrade/StoreMigratorTestUtil.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package upgrade; +package org.neo4j.upgrade; import java.io.File; import java.io.IOException; @@ -34,7 +34,7 @@ public class StoreMigratorTestUtil { StoreMigratorTestUtil() { - // no istance allowed + // no instance allowed } public static ClusterManager.ManagedCluster buildClusterWithMasterDirIn( FileSystemAbstraction fs, @@ -45,15 +45,11 @@ public static ClusterManager.ManagedCluster buildClusterWithMasterDirIn( FileSys fs.deleteRecursively( haRootDir ); ClusterManager clusterManager = new ClusterManager.Builder( haRootDir ) - .withStoreDirInitializer( new ClusterManager.StoreDirInitializer() + .withStoreDirInitializer( ( serverId, storeDir ) -> { - @Override - public void initializeStoreDir( int serverId, File storeDir ) throws IOException + if ( serverId == 1 ) // Initialize dir only for master, others will copy store from it { - if ( serverId == 1 ) // Initialize dir only for master, others will copy store from it - { - FileUtils.copyRecursively( legacyStoreDir, storeDir ); - } + FileUtils.copyRecursively( legacyStoreDir, storeDir ); } } ) .withCluster( clusterOfSize( 3 ) ) diff --git a/enterprise/neo4j-enterprise/src/test/resources/org/neo4j/upgrade/upgradeTest30HighLimitDb.zip b/enterprise/neo4j-enterprise/src/test/resources/org/neo4j/upgrade/upgradeTest30HighLimitDb.zip new file mode 100644 index 0000000000000000000000000000000000000000..154f10c3d05c748678f81f8e45b7bee00fa37e23 GIT binary patch literal 28097 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 F{15q0P1yhd literal 0 HcmV?d00001