-
Notifications
You must be signed in to change notification settings - Fork 2.3k
/
LabelScanStoreHaIT.java
197 lines (176 loc) · 6.9 KB
/
LabelScanStoreHaIT.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
/*
* Copyright (c) 2002-2017 "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 <http://www.gnu.org/licenses/>.
*/
package org.neo4j.kernel.api.impl.labelscan;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import java.util.Collections;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Label;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.factory.TestHighlyAvailableGraphDatabaseFactory;
import org.neo4j.kernel.api.labelscan.LabelScanStore;
import org.neo4j.kernel.extension.KernelExtensionFactory;
import org.neo4j.kernel.impl.ha.ClusterManager;
import org.neo4j.kernel.impl.ha.ClusterManager.ManagedCluster;
import org.neo4j.kernel.lifecycle.LifeSupport;
import org.neo4j.test.TestGraphDatabaseFactory;
import org.neo4j.test.rule.TestDirectory;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.neo4j.helpers.collection.Iterators.count;
import static org.neo4j.kernel.impl.ha.ClusterManager.allAvailabilityGuardsReleased;
import static org.neo4j.kernel.impl.ha.ClusterManager.allSeesAllAsAvailable;
public abstract class LabelScanStoreHaIT
{
@Test
public void shouldCopyLabelScanStoreToNewSlaves() throws Exception
{
// This check is here o check so that the extension provided by this test is selected.
// It can be higher than 3 (number of cluster members) since some members may restart
// some services to switch role.
assertTrue( "Expected initial calls to init to be at least one per cluster member (>= 3), " +
"but was " + monitor.callsTo_init,
monitor.callsTo_init >= 3 );
// GIVEN
// An HA cluster where the master started with initial data consisting
// of a couple of nodes, each having one or more properties.
// The cluster starting up should have the slaves copy their stores from the master
// and get the label scan store with it.
// THEN
assertEquals( "Expected none to build their label scan store index.",
0, monitor.timesRebuiltWithData );
for ( GraphDatabaseService db : cluster.getAllMembers() )
{
assertEquals( 2, numberOfNodesHavingLabel( db, Labels.First ) );
assertEquals( 2, numberOfNodesHavingLabel( db, Labels.First ) );
}
}
private long numberOfNodesHavingLabel( GraphDatabaseService db, Label label )
{
try ( Transaction tx = db.beginTx() )
{
long count = count( db.findNodes( label ) );
tx.success();
return count;
}
}
private void createSomeLabeledNodes( GraphDatabaseService db, Label[]... labelArrays )
{
try ( Transaction tx = db.beginTx() )
{
for ( Label[] labels : labelArrays )
{
db.createNode( labels );
}
tx.success();
}
}
private enum Labels implements Label
{
First,
Second
}
@Rule
public final TestDirectory testDirectory = TestDirectory.testDirectory();
private final LifeSupport life = new LifeSupport();
private ManagedCluster cluster;
private final TestMonitor monitor = new TestMonitor();
@Before
public void setUp()
{
KernelExtensionFactory<?> testExtension = labelScanStoreExtension( monitor );
TestHighlyAvailableGraphDatabaseFactory factory = new TestHighlyAvailableGraphDatabaseFactory();
factory.addKernelExtensions( Collections.singletonList( testExtension ) );
ClusterManager clusterManager = new ClusterManager.Builder( testDirectory.directory( "root" ) )
.withDbFactory( factory )
.withStoreDirInitializer( ( serverId, storeDir ) -> {
if ( serverId == 1 )
{
GraphDatabaseService db = new TestGraphDatabaseFactory()
.addKernelExtension( testExtension )
.newEmbeddedDatabaseBuilder( storeDir.getAbsoluteFile() )
.newGraphDatabase();
try
{
createSomeLabeledNodes( db,
new Label[] {Labels.First},
new Label[] {Labels.First, Labels.Second},
new Label[] {Labels.Second} );
}
finally
{
db.shutdown();
}
}
} ).build();
life.add( clusterManager );
life.start();
cluster = clusterManager.getCluster();
cluster.await( allSeesAllAsAvailable() );
cluster.await( allAvailabilityGuardsReleased() );
}
protected abstract KernelExtensionFactory<?> labelScanStoreExtension( LabelScanStore.Monitor monitor );
@After
public void tearDown()
{
life.shutdown();
}
private static class TestMonitor implements LabelScanStore.Monitor
{
private volatile int callsTo_init;
private volatile int timesRebuiltWithData;
@Override
public void init()
{
callsTo_init++;
}
@Override
public void noIndex()
{
}
@Override
public void lockedIndex( Exception e )
{
}
@Override
public void notValidIndex()
{
}
@Override
public void rebuilding()
{
}
@Override
public void rebuilt( long roughNodeCount )
{
// In HA each slave database will startup with an empty database before realizing that
// it needs to copy a store from its master, let alone find its master.
// So we're expecting one call to this method from each slave with node count == 0. Ignore those.
// We're tracking number of times we're rebuilding the index where there's data to rebuild,
// i.e. after the db has been copied from the master.
if ( roughNodeCount > 0 )
{
timesRebuiltWithData++;
}
}
}
}