Skip to content

Commit 8c1fdc1

Browse files
committed
STORM-3871: stop a non-leader nimbus from registering a deleted blob again
The leader deletes a blob's zookeeper nodes one by one. A non-leader that syncs in between downloads the blob from another nimbus and then registers its copy after the key is gone, which registers the key as new, and the other nimbus instances, the leader included, download the blob back from it. Only the leader storing a blob a client uploads may now register a key zookeeper does not know; any other nimbus gets a KeyNotFoundException instead.
1 parent a9fb804 commit 8c1fdc1

5 files changed

Lines changed: 106 additions & 31 deletions

File tree

storm-server/src/main/java/org/apache/storm/blobstore/KeySequenceNumber.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,27 @@ public KeySequenceNumber(String key, NimbusInfo nimbusInfo) {
124124
}
125125

126126
public synchronized int getKeySequenceNumber(CuratorFramework zkClient) throws KeyNotFoundException {
127+
return getKeySequenceNumber(zkClient, true);
128+
}
129+
130+
/**
131+
* Hand over the sequence number for the copy of the key held by this nimbus.
132+
*
133+
* @param zkClient the zookeeper client
134+
* @param mayCreateKey whether the key may be created when zookeeper does not know it, which is only right for the
135+
* leader storing a blob that a client uploads. Any other nimbus mirrors a key the leader created,
136+
* so a key zookeeper does not know was deleted and must not be registered again.
137+
* @return the sequence number
138+
* @throws KeyNotFoundException if the key is not in zookeeper and may not be created, or it is deleted meanwhile
139+
*/
140+
public synchronized int getKeySequenceNumber(CuratorFramework zkClient, boolean mayCreateKey) throws KeyNotFoundException {
127141
TreeSet<Integer> sequenceNumbers = new TreeSet<Integer>();
128142
try {
129143
// Key has not been created yet and it is the first time it is being created
130144
if (zkClient.checkExists().forPath(BlobStoreUtils.getBlobStoreSubtree() + "/" + key) == null) {
145+
if (!mayCreateKey) {
146+
throw new KeeperException.NoNodeException(BlobStoreUtils.getBlobStoreSubtree() + "/" + key);
147+
}
131148
zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT)
132149
.withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE).forPath(BLOBSTORE_MAX_KEY_SEQUENCE_SUBTREE + "/" + key);
133150
zkClient.setData().forPath(BLOBSTORE_MAX_KEY_SEQUENCE_SUBTREE + "/" + key,

storm-server/src/main/java/org/apache/storm/blobstore/LocalFsBlobStore.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,14 +139,26 @@ private void setupBlobstore() throws AuthorizationException, KeyNotFoundExceptio
139139
LOG.debug("Creating list of key entries for blobstore inside zookeeper {} local {}", activeKeys, activeLocalKeys);
140140
for (String key : activeLocalKeys) {
141141
try {
142-
state.setupBlob(key, nimbusInfo, getVersionForKey(key, nimbusInfo, zkClient));
142+
state.setupBlob(key, nimbusInfo, getVersionForKey(key, nimbusInfo, zkClient, false));
143143
} catch (KeyNotFoundException e) {
144144
// invalid key, remove it from blobstore
145145
store.deleteBlob(key, NIMBUS_SUBJECT);
146146
}
147147
}
148148
}
149149

150+
/**
151+
* Tell whether this nimbus is the leader, which is the only one that may register a key zookeeper does not know.
152+
* Without a leader elector there is a single nimbus, which is then the leader.
153+
*/
154+
private boolean isLeader() {
155+
try {
156+
return leaderElector == null || leaderElector.isLeader();
157+
} catch (Exception e) {
158+
throw new RuntimeException(e);
159+
}
160+
}
161+
150162

151163
private void blobSync() throws Exception {
152164
if ("distributed".equals(conf.get(Config.STORM_CLUSTER_MODE))) {
@@ -216,11 +228,14 @@ public AtomicOutputStream createBlob(String key, SettableBlobMeta meta, Subject
216228
}
217229
BlobStoreFileOutputStream outputStream = null;
218230
try {
231+
//Taken before anything is written, so that a non-leader downloading a key that was deleted meanwhile is
232+
//refused without leaving a copy behind.
233+
int version = getVersionForKey(key, this.nimbusInfo, zkClient, isLeader());
219234
outputStream = new BlobStoreFileOutputStream(fbs.write(META_PREFIX + key, true));
220235
outputStream.write(Utils.thriftSerialize(meta));
221236
outputStream.close();
222237
outputStream = null;
223-
this.stormClusterState.setupBlob(key, this.nimbusInfo, getVersionForKey(key, this.nimbusInfo, zkClient));
238+
this.stormClusterState.setupBlob(key, this.nimbusInfo, version);
224239
return new BlobStoreFileOutputStream(fbs.write(DATA_PREFIX + key, true));
225240
} catch (IOException e) {
226241
throw new RuntimeException(e);

storm-server/src/main/java/org/apache/storm/daemon/nimbus/Nimbus.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -777,8 +777,18 @@ static List<String> getKeyListFromId(Map<String, Object> conf, String id) {
777777

778778
public static int getVersionForKey(String key, NimbusInfo nimbusInfo,
779779
CuratorFramework zkClient) throws KeyNotFoundException {
780+
return getVersionForKey(key, nimbusInfo, zkClient, true);
781+
}
782+
783+
/**
784+
* Get the version to register the copy of a blob held by a nimbus under.
785+
*
786+
* @see KeySequenceNumber#getKeySequenceNumber(CuratorFramework, boolean)
787+
*/
788+
public static int getVersionForKey(String key, NimbusInfo nimbusInfo,
789+
CuratorFramework zkClient, boolean mayCreateKey) throws KeyNotFoundException {
780790
KeySequenceNumber kseq = new KeySequenceNumber(key, nimbusInfo);
781-
return kseq.getKeySequenceNumber(zkClient);
791+
return kseq.getKeySequenceNumber(zkClient, mayCreateKey);
782792
}
783793

784794
private static StormTopology readStormTopology(String topoId, TopoCache tc) throws KeyNotFoundException, AuthorizationException,
@@ -4425,7 +4435,9 @@ public void createStateInZookeeper(String key) throws TException {
44254435
BlobStore store = blobStore;
44264436
NimbusInfo ni = nimbusHostPortInfo;
44274437
if (store instanceof LocalFsBlobStore) {
4428-
state.setupBlob(key, ni, getVersionForKey(key, ni, zkClient));
4438+
//A non-leader only registers its copy of a key the leader created. If zookeeper does not know the key
4439+
//any more it was deleted while the copy was downloaded, and registering it would bring it back.
4440+
state.setupBlob(key, ni, getVersionForKey(key, ni, zkClient, isLeader()));
44294441
}
44304442
LOG.debug("Created state in zookeeper {} {} {}", state, store, ni);
44314443
} catch (KeyNotFoundException e) {

storm-server/src/test/java/org/apache/storm/blobstore/KeySequenceNumberTest.java

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@
1919
package org.apache.storm.blobstore;
2020

2121
import static org.junit.jupiter.api.Assertions.assertEquals;
22+
import static org.junit.jupiter.api.Assertions.assertNull;
23+
import static org.junit.jupiter.api.Assertions.assertThrows;
2224

25+
import org.apache.storm.generated.KeyNotFoundException;
2326
import org.apache.storm.nimbus.NimbusInfo;
2427
import org.apache.storm.shade.org.apache.curator.framework.CuratorFramework;
2528
import org.apache.storm.shade.org.apache.curator.framework.CuratorFrameworkFactory;
@@ -29,52 +32,65 @@
2932

3033
class KeySequenceNumberTest {
3134
private static final String KEY = "dep-lib-11111111-1111-1111-1111-111111111111.jar";
35+
private static final String KEY_PATH = "/blobstore/" + KEY;
36+
private static final String MAX_SEQUENCE_PATH = "/blobstoremaxkeysequencenumber/" + KEY;
3237
private static final NimbusInfo LEADER = new NimbusInfo("nimbus-1", 6627, false);
33-
private static final NimbusInfo PEER = new NimbusInfo("nimbus-0", 6627, false);
38+
private static final NimbusInfo PEER = new NimbusInfo("nimbus-2", 6627, false);
3439

3540
/**
36-
* Replays the blob store state changes behind the nimbus log reported on STORM-3871, where a dependency blob that
37-
* was removed when its topology was cleaned up showed up on the leader again, registered at version 0 and then 1.
41+
* Replays the blob store state changes behind the nimbus logs reported on STORM-3871. A non-leader that downloaded
42+
* a blob while the leader deleted it registered the key again as new, and every other nimbus, the leader included,
43+
* then downloaded the blob back from it.
3844
*/
3945
@Test
40-
void aBlobThatAnotherNimbusRegistersAgainAfterItWasDeletedIsRecreatedOnTheLeader() throws Exception {
46+
void aNonLeaderCannotRegisterAKeyAgainThatWasDeletedWhileItDownloadedIt() throws Exception {
4147
try (InProcessZookeeper zk = new InProcessZookeeper();
42-
CuratorFramework zkClient = CuratorFrameworkFactory.newClient("localhost:" + zk.getPort(),
43-
new ExponentialBackoffRetry(1000, 3))) {
44-
zkClient.start();
45-
48+
CuratorFramework zkClient = newClient(zk)) {
4649
// the client uploads the dependency: createBlob, then createStateInZookeeper when it closes the stream
47-
assertEquals(1, register(zkClient, LEADER));
48-
assertEquals(2, register(zkClient, LEADER));
50+
assertEquals(1, register(zkClient, LEADER, true));
51+
assertEquals(2, register(zkClient, LEADER, true));
4952

5053
// the topology is cleaned up and the leader deletes the blob, as LocalFsBlobStore#deleteBlob does
51-
zkClient.delete().deletingChildrenIfNeeded().forPath("/blobstore/" + KEY);
52-
zkClient.delete().deletingChildrenIfNeeded().forPath("/blobstoremaxkeysequencenumber/" + KEY);
54+
zkClient.delete().deletingChildrenIfNeeded().forPath(KEY_PATH);
55+
zkClient.delete().deletingChildrenIfNeeded().forPath(MAX_SEQUENCE_PATH);
56+
57+
// the non-leader finishes its download and would register the key as if it were new
58+
assertThrows(KeyNotFoundException.class, () -> register(zkClient, PEER, false));
59+
assertNull(zkClient.checkExists().forPath(KEY_PATH));
60+
assertNull(zkClient.checkExists().forPath(MAX_SEQUENCE_PATH));
61+
}
62+
}
5363

54-
// another nimbus, which still has a copy, registers the key again as if it were new
55-
assertEquals(1, register(zkClient, PEER));
64+
@Test
65+
void aNonLeaderStillRegistersItsCopyOfAKeyTheLeaderCreated() throws Exception {
66+
try (InProcessZookeeper zk = new InProcessZookeeper();
67+
CuratorFramework zkClient = newClient(zk)) {
68+
assertEquals(1, register(zkClient, LEADER, true));
5669

57-
// a request for the key makes the leader download it back from that nimbus: createBlob, then
58-
// createStateInZookeeper, which are the set-path lines ending in -0 and -1 in the report
59-
assertEquals(0, register(zkClient, LEADER));
60-
assertEquals(1, register(zkClient, LEADER));
70+
assertEquals(0, register(zkClient, PEER, false));
6171
}
6272
}
6373

74+
private static CuratorFramework newClient(InProcessZookeeper zk) {
75+
CuratorFramework zkClient = CuratorFrameworkFactory.newClient("localhost:" + zk.getPort(),
76+
new ExponentialBackoffRetry(1000, 3));
77+
zkClient.start();
78+
return zkClient;
79+
}
80+
6481
/**
6582
* Do what IStormClusterState#setupBlob does with the version KeySequenceNumber hands out.
6683
*/
67-
private static int register(CuratorFramework zkClient, NimbusInfo nimbus) throws Exception {
68-
int version = new KeySequenceNumber(KEY, nimbus).getKeySequenceNumber(zkClient);
69-
String parent = "/blobstore/" + KEY;
70-
if (zkClient.checkExists().forPath(parent) != null) {
71-
for (String child : zkClient.getChildren().forPath(parent)) {
84+
private static int register(CuratorFramework zkClient, NimbusInfo nimbus, boolean mayCreateKey) throws Exception {
85+
int version = new KeySequenceNumber(KEY, nimbus).getKeySequenceNumber(zkClient, mayCreateKey);
86+
if (zkClient.checkExists().forPath(KEY_PATH) != null) {
87+
for (String child : zkClient.getChildren().forPath(KEY_PATH)) {
7288
if (child.startsWith(nimbus.toHostPortString())) {
73-
zkClient.delete().forPath(parent + "/" + child);
89+
zkClient.delete().forPath(KEY_PATH + "/" + child);
7490
}
7591
}
7692
}
77-
zkClient.create().creatingParentsIfNeeded().forPath(parent + "/" + nimbus.toHostPortString() + "-" + version);
93+
zkClient.create().creatingParentsIfNeeded().forPath(KEY_PATH + "/" + nimbus.toHostPortString() + "-" + version);
7894
return version;
7995
}
8096
}

storm-server/src/test/java/org/apache/storm/daemon/nimbus/NimbusTest.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
import static org.junit.jupiter.api.Assertions.assertNull;
9595
import static org.junit.jupiter.api.Assertions.assertSame;
9696
import static org.mockito.ArgumentMatchers.any;
97+
import static org.mockito.ArgumentMatchers.anyBoolean;
9798
import static org.mockito.ArgumentMatchers.anyString;
9899
import static org.mockito.ArgumentMatchers.eq;
99100
import static org.mockito.Mockito.doThrow;
@@ -266,14 +267,28 @@ void testCreateStateInZookeeperWhenFailToSetupBlobWithRuntimeExceptionThrowsRunt
266267
@Test
267268
void testCreateStateInZookeeperWhenKeyNotFoundHandlesException() throws Exception {
268269
try (MockedConstruction<KeySequenceNumber> keySequenceNumber = mockConstruction(KeySequenceNumber.class, (mock, context) ->
269-
when(mock.getKeySequenceNumber(any())).thenThrow(new KeyNotFoundException("Failed to setup blob")))) {
270+
when(mock.getKeySequenceNumber(any(), anyBoolean())).thenThrow(new KeyNotFoundException("Failed to setup blob")))) {
270271
nimbus.createStateInZookeeper(BLOB_FILE_KEY);
271272

272-
verify(keySequenceNumber.constructed().get(0)).getKeySequenceNumber(any());
273+
verify(keySequenceNumber.constructed().get(0)).getKeySequenceNumber(any(), anyBoolean());
273274
verify(stormClusterState, never()).setupBlob(eq(BLOB_FILE_KEY), eq(nimbusInfo), any());
274275
}
275276
}
276277

278+
@Test
279+
void testCreateStateInZookeeperOnlyLetsTheLeaderRegisterAKeyZookeeperDoesNotKnow() throws Exception {
280+
try (MockedConstruction<KeySequenceNumber> keySequenceNumber = mockConstruction(KeySequenceNumber.class)) {
281+
when(leaderElector.isLeader()).thenReturn(false);
282+
nimbus.createStateInZookeeper(BLOB_FILE_KEY);
283+
when(leaderElector.isLeader()).thenReturn(true);
284+
nimbus.createStateInZookeeper(BLOB_FILE_KEY);
285+
286+
// a non-leader registering a key zookeeper does not know would bring back a key that was deleted
287+
verify(keySequenceNumber.constructed().get(0)).getKeySequenceNumber(any(), eq(false));
288+
verify(keySequenceNumber.constructed().get(1)).getKeySequenceNumber(any(), eq(true));
289+
}
290+
}
291+
277292
@Test
278293
void testListBlobsOnlyReturnsKeysTheCallerMayReadTheMetadataOf() throws Exception {
279294
when(localBlobStore.listKeys()).thenReturn(List.of("readable-key", "other-users-key").iterator());

0 commit comments

Comments
 (0)