Skip to content

Commit 084ccef

Browse files
committed
listObjects leak fix and test
1 parent 74fd7f3 commit 084ccef

File tree

3 files changed

+112
-1
lines changed

3 files changed

+112
-1
lines changed

java-manta-client/src/main/java/com/joyent/manta/client/MantaClient.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -714,7 +714,8 @@ public Stream<MantaObject> listObjects(final String path) throws IOException {
714714
StreamSupport.stream(Spliterators.spliteratorUnknownSize(
715715
itr, Spliterator.ORDERED | Spliterator.NONNULL), false);
716716

717-
Stream<MantaObject> stream = backingStream.map(item -> {
717+
Stream<MantaObject> stream = backingStream.onClose(itr::close)
718+
.map(item -> {
718719
String name = Objects.toString(item.get("name"));
719720
String mtime = Objects.toString(item.get("mtime"));
720721
String type = Objects.toString(item.get("type"));
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.joyent.manta.client;
2+
3+
import com.joyent.manta.config.ConfigContext;
4+
import com.joyent.manta.config.TestConfigContext;
5+
import org.mockito.Mockito;
6+
import org.testng.annotations.AfterTest;
7+
import org.testng.annotations.Test;
8+
9+
import java.io.IOException;
10+
import java.util.stream.Stream;
11+
12+
import static org.mockito.Matchers.anyString;
13+
import static org.mockito.Mockito.doReturn;
14+
import static org.mockito.Mockito.mock;
15+
import static org.mockito.Mockito.spy;
16+
import static org.mockito.Mockito.verify;
17+
import static org.mockito.Mockito.when;
18+
19+
public class MantaClientTest {
20+
21+
@AfterTest
22+
public void teardown() {
23+
Mockito.validateMockitoUsage();
24+
}
25+
26+
@Test
27+
public void listObjectsDoesNotLeakConnections() throws IOException {
28+
// BasicHttpClientConnectionManager maintains a single connection
29+
30+
final MantaDirectoryListingIterator iteratorMock = mock(MantaDirectoryListingIterator.class);
31+
when(iteratorMock.hasNext()).thenReturn(true);
32+
33+
final ConfigContext config = TestConfigContext.generateKeyPairBackedConfig().right.setMantaUser("user");
34+
final MantaClient client = new MantaClient(config);
35+
final MantaClient clientSpy = spy(client);
36+
doReturn(iteratorMock).when(clientSpy).streamingIterator(anyString());
37+
38+
final Stream<MantaObject> listing = clientSpy.listObjects("/");
39+
listing.close();
40+
41+
verify(iteratorMock).close();
42+
}
43+
}

java-manta-client/src/test/java/com/joyent/manta/config/TestConfigContext.java

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,18 @@
77
*/
88
package com.joyent.manta.config;
99

10+
import com.joyent.http.signature.KeyFingerprinter;
11+
import org.apache.commons.lang3.tuple.ImmutablePair;
12+
import org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator;
13+
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
14+
import org.bouncycastle.openssl.jcajce.JcePEMEncryptorBuilder;
15+
16+
import java.io.IOException;
17+
import java.io.StringWriter;
1018
import java.net.URL;
19+
import java.security.KeyPair;
20+
import java.security.KeyPairGenerator;
21+
import java.security.NoSuchAlgorithmException;
1122
import java.util.Properties;
1223

1324
/**
@@ -97,4 +108,60 @@ static ConfigContext buildTestContext(String mantaUrl,
97108

98109
return testConfig;
99110
}
111+
112+
public static ImmutablePair<KeyPair, BaseChainedConfigContext> generateKeyPairBackedConfig() {
113+
return generateKeyPairBackedConfig(null);
114+
}
115+
116+
/**
117+
* Some test cases need a direct reference to a KeyPair along with it's associated config. Manually calling
118+
* KeyPairFactory with a half-baked config can get cumbersome, so let's build a ConfigContext which has
119+
* everything ready and supplies the relevant KeyPair.
120+
*
121+
* @return the generated keypair and a config which uses a serialized version of that keypair
122+
*/
123+
public static ImmutablePair<KeyPair, BaseChainedConfigContext> generateKeyPairBackedConfig(final String passphrase) {
124+
final KeyPair keyPair;
125+
try {
126+
keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
127+
} catch (final NoSuchAlgorithmException impossible) {
128+
throw new Error(impossible); // "RSA" is always provided
129+
}
130+
131+
final Object keySerializer;
132+
if (passphrase != null) {
133+
try {
134+
keySerializer = new JcaMiscPEMGenerator(
135+
keyPair.getPrivate(),
136+
new JcePEMEncryptorBuilder("AES-128-CBC").build(passphrase.toCharArray()));
137+
} catch (IOException e) {
138+
throw new RuntimeException(e);
139+
}
140+
} else {
141+
keySerializer = keyPair.getPrivate();
142+
}
143+
144+
final String keyContent;
145+
try (final StringWriter content = new StringWriter();
146+
final JcaPEMWriter writer = new JcaPEMWriter(content)) {
147+
writer.writeObject(keySerializer);
148+
writer.flush();
149+
keyContent = content.toString();
150+
} catch (IOException e) {
151+
throw new RuntimeException(e);
152+
}
153+
154+
final BaseChainedConfigContext config = new ChainedConfigContext(DEFAULT_CONFIG)
155+
// we need to unset the key path in case one exists at ~/.ssh/id_rsa
156+
// see the static initializer in DefaultsConfigContext
157+
.setMantaKeyPath(null)
158+
.setPrivateKeyContent(keyContent)
159+
.setMantaKeyId(KeyFingerprinter.md5Fingerprint(keyPair));
160+
161+
if (passphrase != null) {
162+
config.setPassword(passphrase);
163+
}
164+
165+
return new ImmutablePair<>(keyPair, config);
166+
}
100167
}

0 commit comments

Comments
 (0)