Skip to content

Commit c1f7c04

Browse files
SNOW-3344317 normalize S3 client URI scheme before attempting endpointOverride
1 parent f707e71 commit c1f7c04

File tree

2 files changed

+166
-1
lines changed

2 files changed

+166
-1
lines changed

src/main/java/net/snowflake/client/internal/jdbc/cloud/storage/SnowflakeS3Client.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ private void setupSnowflakeS3Client(
178178
if (!endpointForOverride.startsWith("https://")
179179
&& !endpointForOverride.startsWith("http://")) {
180180
logger.debug(
181-
"SNOW-3344317: stageEndPoint has no scheme: {}", this.stageEndPoint);
181+
"AWS S3 Client: stage endpoint {} has no scheme, normalizing for URI creation.", this.stageEndPoint);
182182
endpointForOverride = "https://" + endpointForOverride;
183183
}
184184
clientBuilder.endpointOverride(URI.create(endpointForOverride));

src/test/java/net/snowflake/client/internal/jdbc/cloud/storage/SnowflakeS3ClientTest.java

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,25 @@
11
package net.snowflake.client.internal.jdbc.cloud.storage;
22

33
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertFalse;
5+
import static org.junit.jupiter.api.Assertions.assertNotNull;
6+
import static org.junit.jupiter.api.Assertions.assertNull;
7+
import static org.junit.jupiter.api.Assertions.assertTrue;
48

9+
import java.lang.reflect.Field;
10+
import java.net.URI;
11+
import java.util.HashMap;
12+
import java.util.Map;
13+
import java.util.Optional;
14+
import net.snowflake.client.api.exception.SnowflakeSQLException;
515
import org.junit.jupiter.api.Test;
16+
import software.amazon.awssdk.services.s3.S3AsyncClient;
617

18+
/**
19+
* Unit tests for {@link SnowflakeS3Client} endpoint selection (GS {@code stageInfo.endPoint} and
20+
* regional URL mode). Uses {@link S3AsyncClient#serviceClientConfiguration()}{@code
21+
* .endpointOverride()} to read the effective override.
22+
*/
723
public class SnowflakeS3ClientTest {
824

925
@Test
@@ -14,4 +30,153 @@ public void shouldDetermineDomainForRegion() {
1430
assertEquals(
1531
"amazonaws.com.cn", SnowflakeS3Client.getDomainSuffixForRegionalUrl("CN-NORTHWEST-1"));
1632
}
33+
34+
/** Documents why bare hostnames must be normalized before {@code endpointOverride(URI)}. */
35+
@Test
36+
public void uriCreate_bareAwsHostname_hasNullScheme() {
37+
assertNull(URI.create("s3.us-west-2.amazonaws.com").getScheme());
38+
}
39+
40+
@Test
41+
public void endpointOverride_stageEndPointWithoutScheme_prependsHttps() throws Exception {
42+
SnowflakeS3Client client =
43+
newClient(
44+
"eu-west-1",
45+
"s3.eu-west-1.amazonaws.com",
46+
false,
47+
/* isClientSideEncrypted */ false);
48+
try {
49+
URI override = endpointOverride(client).orElseThrow();
50+
assertEquals("https", override.getScheme());
51+
assertEquals("s3.eu-west-1.amazonaws.com", override.getHost());
52+
} finally {
53+
client.shutdown();
54+
}
55+
}
56+
57+
@Test
58+
public void endpointOverride_stageEndPointWithHttps_unchanged() throws Exception {
59+
String url = "https://s3-fips.us-gov-west-1.amazonaws.com";
60+
SnowflakeS3Client client =
61+
newClient("us-gov-west-1", url, false, /* isClientSideEncrypted */ false);
62+
try {
63+
URI override = endpointOverride(client).orElseThrow();
64+
assertEquals(URI.create(url), override);
65+
} finally {
66+
client.shutdown();
67+
}
68+
}
69+
70+
@Test
71+
public void endpointOverride_stageEndPointWithHttp_unchanged() throws Exception {
72+
String url = "http://minio.local:9000";
73+
SnowflakeS3Client client =
74+
newClient("us-east-1", url, false, /* isClientSideEncrypted */ false);
75+
try {
76+
URI override = endpointOverride(client).orElseThrow();
77+
assertEquals("http", override.getScheme());
78+
assertEquals("minio.local", override.getHost());
79+
assertEquals(9000, override.getPort());
80+
} finally {
81+
client.shutdown();
82+
}
83+
}
84+
85+
@Test
86+
public void endpointOverride_useS3RegionalUrl_commercialRegion() throws Exception {
87+
SnowflakeS3Client client =
88+
newClient(
89+
"us-west-2",
90+
/* stageEndPoint */ null,
91+
/* useS3RegionalUrl */ true,
92+
/* isClientSideEncrypted */ false);
93+
try {
94+
URI override = endpointOverride(client).orElseThrow();
95+
assertEquals(URI.create("https://s3.us-west-2.amazonaws.com"), override);
96+
} finally {
97+
client.shutdown();
98+
}
99+
}
100+
101+
@Test
102+
public void endpointOverride_useS3RegionalUrl_chinaRegion() throws Exception {
103+
SnowflakeS3Client client =
104+
newClient(
105+
"cn-northwest-1",
106+
/* stageEndPoint */ null,
107+
/* useS3RegionalUrl */ true,
108+
/* isClientSideEncrypted */ false);
109+
try {
110+
URI override = endpointOverride(client).orElseThrow();
111+
assertEquals(URI.create("https://s3.cn-northwest-1.amazonaws.com.cn"), override);
112+
} finally {
113+
client.shutdown();
114+
}
115+
}
116+
117+
@Test
118+
public void endpointOverride_noStageEndPoint_noRegionalUrl_noOverride() throws Exception {
119+
SnowflakeS3Client client =
120+
newClient(
121+
"eu-central-1",
122+
/* stageEndPoint */ null,
123+
/* useS3RegionalUrl */ false,
124+
/* isClientSideEncrypted */ false);
125+
try {
126+
assertFalse(endpointOverride(client).isPresent());
127+
} finally {
128+
client.shutdown();
129+
}
130+
}
131+
132+
/** Regression: scheme-less {@code stageEndPoint} must not NPE inside AWS SDK v2 client build. */
133+
@Test
134+
public void constructor_schemeLessStageEndPoint_doesNotThrowNpe() throws Exception {
135+
SnowflakeS3Client client = null;
136+
try {
137+
client =
138+
newClient(
139+
"us-west-2",
140+
"s3.us-west-2.amazonaws.com",
141+
false,
142+
/* isClientSideEncrypted */ false);
143+
assertNotNull(client);
144+
assertTrue(endpointOverride(client).isPresent());
145+
assertNotNull(endpointOverride(client).get().getScheme());
146+
} finally {
147+
if (client != null) {
148+
client.shutdown();
149+
}
150+
}
151+
}
152+
153+
private static SnowflakeS3Client newClient(
154+
String stageRegion,
155+
String stageEndPoint,
156+
boolean useS3RegionalUrl,
157+
boolean isClientSideEncrypted)
158+
throws SnowflakeSQLException {
159+
Map<String, String> creds = new HashMap<>();
160+
creds.put("AWS_KEY_ID", "AKIA_TEST");
161+
creds.put("AWS_SECRET_KEY", "testSecretAccessKey");
162+
SnowflakeS3Client.ClientConfiguration config =
163+
new SnowflakeS3Client.ClientConfiguration(2, 3, 5_000, 5_000);
164+
return new SnowflakeS3Client(
165+
creds,
166+
config,
167+
/* encMat */ null,
168+
/* proxyProperties */ null,
169+
stageRegion,
170+
stageEndPoint,
171+
isClientSideEncrypted,
172+
/* session */ null,
173+
useS3RegionalUrl);
174+
}
175+
176+
private static Optional<URI> endpointOverride(SnowflakeS3Client client) throws Exception {
177+
Field f = SnowflakeS3Client.class.getDeclaredField("amazonClient");
178+
f.setAccessible(true);
179+
S3AsyncClient aws = (S3AsyncClient) f.get(client);
180+
return aws.serviceClientConfiguration().endpointOverride();
181+
}
17182
}

0 commit comments

Comments
 (0)