HDDS-525. Support virtual-hosted style URLs. Contributed by Bharat Viswanadham
Project: http://git-wip-us.apache.org/repos/asf/hadoop/repo Commit: http://git-wip-us.apache.org/repos/asf/hadoop/commit/4eff629a Tree: http://git-wip-us.apache.org/repos/asf/hadoop/tree/4eff629a Diff: http://git-wip-us.apache.org/repos/asf/hadoop/diff/4eff629a Branch: refs/heads/HDFS-13532 Commit: 4eff629ab3a330e8f1efe92857e76235cb412ef4 Parents: 59d5af2 Author: Bharat Viswanadham <[email protected]> Authored: Mon Oct 1 13:16:08 2018 -0700 Committer: Bharat Viswanadham <[email protected]> Committed: Mon Oct 1 13:16:08 2018 -0700 ---------------------------------------------------------------------- .../common/src/main/resources/ozone-default.xml | 78 ++++++++- hadoop-ozone/integration-test/pom.xml | 4 + .../ozone/TestOzoneConfigurationFields.java | 4 +- .../hadoop/ozone/s3/S3GatewayConfigKeys.java | 1 + .../hadoop/ozone/s3/VirtualHostStyleFilter.java | 143 ++++++++++++++++ .../ozone/s3/TestVirtualHostStyleFilter.java | 163 +++++++++++++++++++ .../apache/hadoop/ozone/s3/package-info.java | 21 +++ 7 files changed, 411 insertions(+), 3 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/hadoop/blob/4eff629a/hadoop-hdds/common/src/main/resources/ozone-default.xml ---------------------------------------------------------------------- diff --git a/hadoop-hdds/common/src/main/resources/ozone-default.xml b/hadoop-hdds/common/src/main/resources/ozone-default.xml index f7681e8..b7c967d 100644 --- a/hadoop-hdds/common/src/main/resources/ozone-default.xml +++ b/hadoop-hdds/common/src/main/resources/ozone-default.xml @@ -1035,12 +1035,12 @@ <property> <name>hadoop.tags.custom</name> - <value>OZONE,MANAGEMENT,SECURITY,PERFORMANCE,DEBUG,CLIENT,SERVER,OM,SCM,CRITICAL,RATIS,CONTAINER,REQUIRED,REST,STORAGE,PIPELINE,STANDALONE</value> + <value>OZONE,MANAGEMENT,SECURITY,PERFORMANCE,DEBUG,CLIENT,SERVER,OM,SCM,CRITICAL,RATIS,CONTAINER,REQUIRED,REST,STORAGE,PIPELINE,STANDALONE,S3GATEWAY</value> </property> <property> <name>ozone.tags.system</name> - <value>OZONE,MANAGEMENT,SECURITY,PERFORMANCE,DEBUG,CLIENT,SERVER,OM,SCM,CRITICAL,RATIS,CONTAINER,REQUIRED,REST,STORAGE,PIPELINE,STANDALONE</value> + <value>OZONE,MANAGEMENT,SECURITY,PERFORMANCE,DEBUG,CLIENT,SERVER,OM,SCM,CRITICAL,RATIS,CONTAINER,REQUIRED,REST,STORAGE,PIPELINE,STANDALONE,S3GATEWAY</value> </property> @@ -1222,4 +1222,78 @@ </description> </property> + <property> + <name>ozone.s3g.authentication.kerberos.principal</name> + <value/> + <tag>OZONE, S3GATEWAY</tag> + <description>The server principal used by Ozone S3Gateway server. This is + typically set to + HTTP/[email protected] The SPNEGO server principal begins with the prefix + HTTP/ by convention.</description> + </property> + + <property> + <name>ozone.s3g.domain.name</name> + <value/> + <tag>OZONE, S3GATEWAY</tag> + <description>List of Ozone S3Gateway domain names. If multiple + domain names to be provided, they should be a "," seperated. + This parameter is only required when virtual host style pattern is + followed.</description> + </property> + + <property> + <name>ozone.s3g.http-address</name> + <value>0.0.0.0:9878</value> + <tag>OZONE, S3GATEWAY</tag> + <description>The address and the base port where the Ozone S3Gateway + Server will + listen on.</description> + </property> + + <property> + <name>ozone.s3g.http-bind-host</name> + <value>0.0.0.0</value> + <tag>OZONE, S3GATEWAY</tag> + <description>The actual address the HTTP server will bind to. If this optional address + is set, it overrides only the hostname portion of ozone.s3g.http-address. + This is useful for making the Ozone S3Gateway HTTP server listen on all + interfaces by setting it to 0.0.0.0.</description> + </property> + + <property> + <name>ozone.s3g.http.enabled</name> + <value>true</value> + <tag>OZONE, S3GATEWAY</tag> + <description>The boolean which enables the Ozone S3Gateway server + .</description> + </property> + + <property> + <name>ozone.s3g.https-address</name> + <value/> + <tag>OZONE, S3GATEWAY</tag> + <description>Ozone S3Gateway serverHTTPS server address and port + .</description> + </property> + + <property> + <name>ozone.s3g.https-bind-host</name> + <value/> + <tag>OZONE, S3GATEWAY</tag> + <description>The actual address the HTTPS server will bind to. If this optional address + is set, it overrides only the hostname portion of ozone.s3g.https-address. + This is useful for making the Ozone S3Gateway HTTPS server listen on all + interfaces by setting it to 0.0.0.0.</description> + </property> + + <property> + <name>ozone.s3g.keytab.file</name> + <value/> + <tag>OZONE, S3GATEWAY</tag> + <description>The keytab file used by the S3Gateway server to login as its + service principal. </description> + </property> + + </configuration> http://git-wip-us.apache.org/repos/asf/hadoop/blob/4eff629a/hadoop-ozone/integration-test/pom.xml ---------------------------------------------------------------------- diff --git a/hadoop-ozone/integration-test/pom.xml b/hadoop-ozone/integration-test/pom.xml index bed2fce..84e86d8 100644 --- a/hadoop-ozone/integration-test/pom.xml +++ b/hadoop-ozone/integration-test/pom.xml @@ -44,6 +44,10 @@ http://maven.apache.org/xsd/maven-4.0.0.xsd"> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> + <artifactId>hadoop-ozone-s3gateway</artifactId> + </dependency> + <dependency> + <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-ozone-client</artifactId> </dependency> <dependency> http://git-wip-us.apache.org/repos/asf/hadoop/blob/4eff629a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestOzoneConfigurationFields.java ---------------------------------------------------------------------- diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestOzoneConfigurationFields.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestOzoneConfigurationFields.java index 909cddf..986618c 100644 --- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestOzoneConfigurationFields.java +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestOzoneConfigurationFields.java @@ -21,6 +21,7 @@ import org.apache.hadoop.conf.TestConfigurationFieldsBase; import org.apache.hadoop.hdds.HddsConfigKeys; import org.apache.hadoop.ozone.om.OMConfigKeys; import org.apache.hadoop.hdds.scm.ScmConfigKeys; +import org.apache.hadoop.ozone.s3.S3GatewayConfigKeys; /** * Tests if configuration constants documented in ozone-defaults.xml. @@ -32,7 +33,8 @@ public class TestOzoneConfigurationFields extends TestConfigurationFieldsBase { xmlFilename = new String("ozone-default.xml"); configurationClasses = new Class[] {OzoneConfigKeys.class, ScmConfigKeys.class, - OMConfigKeys.class, HddsConfigKeys.class}; + OMConfigKeys.class, HddsConfigKeys.class, + S3GatewayConfigKeys.class}; errorIfMissingConfigProps = true; errorIfMissingXmlProps = true; xmlPropsToSkipCompare.add("hadoop.tags.custom"); http://git-wip-us.apache.org/repos/asf/hadoop/blob/4eff629a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/S3GatewayConfigKeys.java ---------------------------------------------------------------------- diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/S3GatewayConfigKeys.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/S3GatewayConfigKeys.java index c340a50..4a5570a 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/S3GatewayConfigKeys.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/S3GatewayConfigKeys.java @@ -44,6 +44,7 @@ public final class S3GatewayConfigKeys { public static final int OZONE_S3G_HTTPS_BIND_PORT_DEFAULT = 9879; public static final String OZONE_S3G_WEB_AUTHENTICATION_KERBEROS_PRINCIPAL = "ozone.s3g.authentication.kerberos.principal"; + public static final String OZONE_S3G_DOMAIN_NAME = "ozone.s3g.domain.name"; /** * Never constructed. http://git-wip-us.apache.org/repos/asf/hadoop/blob/4eff629a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/VirtualHostStyleFilter.java ---------------------------------------------------------------------- diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/VirtualHostStyleFilter.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/VirtualHostStyleFilter.java new file mode 100644 index 0000000..3bd690b --- /dev/null +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/VirtualHostStyleFilter.java @@ -0,0 +1,143 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.ozone.s3; + +import javax.inject.Inject; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.container.PreMatching; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.UriBuilder; +import javax.ws.rs.ext.Provider; + +import java.io.IOException; +import java.net.URI; +import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.google.common.annotations.VisibleForTesting; +import org.apache.hadoop.fs.InvalidRequestException; +import org.apache.hadoop.hdds.conf.OzoneConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.apache.hadoop.ozone.s3.S3GatewayConfigKeys.OZONE_S3G_DOMAIN_NAME; + +/** + * Filter used to convert virtual host style pattern to path style pattern. + */ + +@Provider +@PreMatching +public class VirtualHostStyleFilter implements ContainerRequestFilter { + + private static final Logger LOG = LoggerFactory.getLogger( + VirtualHostStyleFilter.class); + private static final Pattern URL_SCHEME_PATTERN = Pattern.compile("" + + "(?<bucket>(.+))\\.(?<volume>(.+))\\."); + + @Inject + private OzoneConfiguration conf; + private String[] domains; + + @Override + public void filter(ContainerRequestContext requestContext) throws + IOException { + domains = conf.getTrimmedStrings(OZONE_S3G_DOMAIN_NAME); + + if (domains.length == 0) { + // domains is not configured, might be it is path style. + // So, do not continue further, just return. + return; + } + //Get the value of the host + String host = requestContext.getHeaderString(HttpHeaders.HOST); + String domain = getDomainName(host); + + if (domain == null) { + throw getException("Invalid S3 Gateway request {" + requestContext + .getUriInfo().getRequestUri().toString() + " }: No matching domain " + + "{" + Arrays.toString(domains) + "} for the host {" + host + "}"); + } + + LOG.debug("Http header host name is {}", host); + LOG.debug("Domain name matched is {}", domain); + + //Check if we have a Virtual Host style request, host length greater than + // address length means it is virtual host style, we need to convert to + // path style. + if (host.length() > domain.length()) { + String bothNames = host.substring(0, host.length() - domain.length()); + LOG.debug("Both volume name and bucket name is {}", bothNames); + Matcher matcher = URL_SCHEME_PATTERN.matcher(bothNames); + + if (!matcher.matches()) { + throw getException("Invalid S3 Gateway request {" + requestContext + .getUriInfo().getRequestUri().toString() +"}:" +" Host: {" + host + + " is in invalid format"); + } + + String bucketStr = matcher.group("bucket"); + String volumeStr = matcher.group("volume"); + + LOG.debug("bucket {}, volumeStr {}", bucketStr, volumeStr); + + URI baseURI = requestContext.getUriInfo().getBaseUri(); + String currentPath = requestContext.getUriInfo().getPath(); + String newPath = String.format("%s/%s", volumeStr, bucketStr); + if (currentPath != null) { + newPath += String.format("%s", currentPath); + } + URI requestAddr = UriBuilder.fromUri(baseURI).path(newPath).build(); + requestContext.setRequestUri(baseURI, requestAddr); + } + } + + private InvalidRequestException getException(String message) { + return new InvalidRequestException(message); + } + + @VisibleForTesting + public void setConfiguration(OzoneConfiguration config) { + this.conf = config; + } + + + /** + * This method finds the longest match with the domain name. + * @param host + * @return domain name matched with the host. if none of them are matching, + * return null. + */ + private String getDomainName(String host) { + String match = null; + int length=0; + for (String domainVal : domains) { + if (host.endsWith(domainVal)) { + int len = domainVal.length(); + if (len > length) { + length = len; + match = domainVal; + } + } + } + return match; + } + +} http://git-wip-us.apache.org/repos/asf/hadoop/blob/4eff629a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestVirtualHostStyleFilter.java ---------------------------------------------------------------------- diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestVirtualHostStyleFilter.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestVirtualHostStyleFilter.java new file mode 100644 index 0000000..ac8fa87 --- /dev/null +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestVirtualHostStyleFilter.java @@ -0,0 +1,163 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.ozone.s3; + +import org.apache.hadoop.fs.InvalidRequestException; +import org.apache.hadoop.hdds.conf.OzoneConfiguration; +import org.apache.hadoop.test.GenericTestUtils; +import org.glassfish.jersey.internal.PropertiesDelegate; +import org.glassfish.jersey.server.ContainerRequest; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.SecurityContext; +import java.net.URI; + +/** + * This class test virtual host style mapping conversion to path style. + */ +public class TestVirtualHostStyleFilter { + + private static OzoneConfiguration conf; + private static String s3HttpAddr; + + @Before + public void setup() { + conf = new OzoneConfiguration(); + s3HttpAddr = "localhost:9878"; + conf.set(S3GatewayConfigKeys.OZONE_S3G_HTTP_ADDRESS_KEY, s3HttpAddr); + conf.set(S3GatewayConfigKeys.OZONE_S3G_DOMAIN_NAME, s3HttpAddr); + } + + /** + * Create containerRequest object. + * @return ContainerRequest + * @throws Exception + */ + public ContainerRequest createContainerRequest(String host, String path, + boolean virtualHostStyle) + throws Exception { + URI baseUri = new URI("http://" + s3HttpAddr); + URI virtualHostStyleUri; + if (path == null) { + virtualHostStyleUri = new URI("http://" + s3HttpAddr); + } else { + virtualHostStyleUri = new URI("http://" + s3HttpAddr + path); + } + URI pathStyleUri = new URI("http://" + s3HttpAddr + path); + String httpMethod = "DELETE"; + SecurityContext securityContext = Mockito.mock(SecurityContext.class); + PropertiesDelegate propertiesDelegate = Mockito.mock(PropertiesDelegate + .class); + ContainerRequest containerRequest; + if (virtualHostStyle) { + containerRequest = new ContainerRequest(baseUri, virtualHostStyleUri, + httpMethod, securityContext, propertiesDelegate); + containerRequest.header(HttpHeaders.HOST, host); + } else { + containerRequest = new ContainerRequest(baseUri, pathStyleUri, + httpMethod, securityContext, propertiesDelegate); + containerRequest.header(HttpHeaders.HOST, host); + } + return containerRequest; + } + + @Test + public void testVirtualHostStyle() throws Exception { + VirtualHostStyleFilter virtualHostStyleFilter = + new VirtualHostStyleFilter(); + virtualHostStyleFilter.setConfiguration(conf); + + ContainerRequest containerRequest = createContainerRequest("mybucket" + + ".myvolume.localhost:9878", "/myfile", true); + virtualHostStyleFilter.filter(containerRequest); + URI expected = new URI("http://" + s3HttpAddr + + "/myvolume/mybucket/myfile"); + Assert.assertEquals(expected, containerRequest.getRequestUri()); + } + + @Test + public void testPathStyle() throws Exception { + + VirtualHostStyleFilter virtualHostStyleFilter = + new VirtualHostStyleFilter(); + virtualHostStyleFilter.setConfiguration(conf); + + ContainerRequest containerRequest = createContainerRequest(s3HttpAddr, + "/myvolume/mybucket/myfile", false); + virtualHostStyleFilter.filter(containerRequest); + URI expected = new URI("http://" + s3HttpAddr + + "/myvolume/mybucket/myfile"); + Assert.assertEquals(expected, containerRequest.getRequestUri()); + + } + + @Test + public void testVirtualHostStyleWithCreateBucketRequest() throws Exception { + + VirtualHostStyleFilter virtualHostStyleFilter = + new VirtualHostStyleFilter(); + virtualHostStyleFilter.setConfiguration(conf); + + ContainerRequest containerRequest = createContainerRequest("mybucket" + + ".myvolume.localhost:9878", null, true); + virtualHostStyleFilter.filter(containerRequest); + URI expected = new URI("http://" + s3HttpAddr + + "/myvolume/mybucket"); + Assert.assertEquals(expected, containerRequest.getRequestUri()); + + } + + @Test + public void testVirtualHostStyleWithNoMatchingDomain() throws Exception { + + VirtualHostStyleFilter virtualHostStyleFilter = + new VirtualHostStyleFilter(); + virtualHostStyleFilter.setConfiguration(conf); + + ContainerRequest containerRequest = createContainerRequest("mybucket" + + ".myvolume.localhost:9999", null, true); + try { + virtualHostStyleFilter.filter(containerRequest); + } catch (InvalidRequestException ex) { + GenericTestUtils.assertExceptionContains("No matching domain", ex); + } + + } + + @Test + public void testVirtualHostStyleWithoutVolumeName() throws Exception { + + VirtualHostStyleFilter virtualHostStyleFilter = + new VirtualHostStyleFilter(); + virtualHostStyleFilter.setConfiguration(conf); + + ContainerRequest containerRequest = createContainerRequest("mybucket." + + ".localhost:9878", null, true); + try { + virtualHostStyleFilter.filter(containerRequest); + } catch (InvalidRequestException ex) { + GenericTestUtils.assertExceptionContains("invalid format", ex); + } + + } + +} http://git-wip-us.apache.org/repos/asf/hadoop/blob/4eff629a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/package-info.java ---------------------------------------------------------------------- diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/package-info.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/package-info.java new file mode 100644 index 0000000..e7e04ab --- /dev/null +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/package-info.java @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Unit tests for the bucket related rest endpoints. + */ +package org.apache.hadoop.ozone.s3; \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
