diff --git a/hadoop-ozone/s3gateway/pom.xml b/hadoop-ozone/s3gateway/pom.xml index 7b02c85215..5eb02320ca 100644 --- a/hadoop-ozone/s3gateway/pom.xml +++ b/hadoop-ozone/s3gateway/pom.xml @@ -62,6 +62,11 @@ jersey-hk2 2.27 + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + 2.9.0 + javax.enterprise cdi-api diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/EndpointBase.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/EndpointBase.java index bfbeeec227..f20d18278f 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/EndpointBase.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/EndpointBase.java @@ -26,12 +26,20 @@ import org.apache.hadoop.ozone.client.OzoneVolume; import com.google.common.annotations.VisibleForTesting; +import org.apache.hadoop.ozone.s3.exception.OS3Exception; +import org.apache.hadoop.ozone.s3.exception.S3ErrorTable; +import org.apache.hadoop.ozone.s3.exception.S3ErrorTable.Resource; +import org.apache.hadoop.ozone.web.utils.OzoneUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Basic helpers for all the REST endpoints. */ public class EndpointBase { + private static final Logger LOG = + LoggerFactory.getLogger(EndpointBase.class); @Inject private OzoneClient client; @@ -41,13 +49,16 @@ protected OzoneBucket getBucket(String volumeName, String bucketName) } protected OzoneBucket getBucket(OzoneVolume volume, String bucketName) - throws IOException { - OzoneBucket bucket = null; + throws OS3Exception, IOException { + OzoneBucket bucket; try { bucket = volume.getBucket(bucketName); - } catch (Exception ex) { + } catch (IOException ex) { + LOG.error("Error occurred is {}", ex); if (ex.getMessage().contains("NOT_FOUND")) { - throw new NotFoundException("Bucket" + bucketName + " is not found"); + OS3Exception oex = S3ErrorTable.newError(S3ErrorTable.NO_SUCH_BUCKET, + OzoneUtils.getRequestID(), Resource.BUCKET); + throw oex; } else { throw ex; } diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/OS3Exception.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/OS3Exception.java new file mode 100644 index 0000000000..722a4a1bd2 --- /dev/null +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/OS3Exception.java @@ -0,0 +1,161 @@ +/* + * 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.exception; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlTransient; +import javax.xml.bind.annotation.XmlRootElement; + + +/** + * This class represents exceptions raised from Ozone S3 service. + * + * Ref:https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html + */ +@XmlRootElement(name = "Error") +@XmlAccessorType(XmlAccessType.NONE) +public class OS3Exception extends Exception { + private static final Logger LOG = + LoggerFactory.getLogger(OS3Exception.class); + private static ObjectMapper mapper; + + static { + mapper = new XmlMapper(); + mapper.registerModule(new JaxbAnnotationModule()); + mapper.enable(SerializationFeature.INDENT_OUTPUT); + } + @XmlElement(name = "Code") + private String code; + + @XmlElement(name = "Message") + private String errorMessage; + + @XmlElement(name = "Resource") + private String resource; + + @XmlElement(name = "RequestId") + private String requestId; + + @XmlTransient + private int httpCode; + + public OS3Exception() { + //Added for JaxB. + } + + /** + * Create an object OS3Exception. + * @param codeVal + * @param messageVal + * @param requestIdVal + * @param resourceVal + */ + public OS3Exception(String codeVal, String messageVal, String requestIdVal, + String resourceVal) { + this.code = codeVal; + this.errorMessage = messageVal; + this.requestId = requestIdVal; + this.resource = resourceVal; + } + + /** + * Create an object OS3Exception. + * @param codeVal + * @param messageVal + * @param httpCode + */ + public OS3Exception(String codeVal, String messageVal, int httpCode) { + this.code = codeVal; + this.errorMessage = messageVal; + this.httpCode = httpCode; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public String getRequestId() { + return requestId; + } + + public void setRequestId(String requestId) { + this.requestId = requestId; + } + + public String getResource() { + return resource; + } + + public void setResource(String resource) { + this.resource = resource; + } + + public int getHttpCode() { + return httpCode; + } + + public void setHttpCode(int httpCode) { + this.httpCode = httpCode; + } + + public String toXml() { + try { + String val = mapper.writeValueAsString(this); + LOG.debug("toXml val is {}", val); + String xmlLine = "\n" + + val; + return xmlLine; + } catch (Exception ex) { + LOG.error("Exception occurred {}", ex); + } + + //When we get exception log it, and return exception as xml from actual + // exception data. So, falling back to construct from exception. + String formatString = "" + + "" + + "%s" + + "%s" + + "%s" + + "%s" + + ""; + return String.format(formatString, this.getCode(), + this.getErrorMessage(), this.getResource(), + this.getRequestId()); + } +} diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/OS3ExceptionMapper.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/OS3ExceptionMapper.java new file mode 100644 index 0000000000..3f39d722d0 --- /dev/null +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/OS3ExceptionMapper.java @@ -0,0 +1,41 @@ +/* + * 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.exception; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; + +/** + * Class the represents various errors returned by the + * Ozone S3 service. + */ +@Provider +public class OS3ExceptionMapper implements ExceptionMapper { + private static final Logger LOG = + LoggerFactory.getLogger(OS3ExceptionMapper.class); + @Override + public Response toResponse(OS3Exception exception) { + LOG.debug("Returning exception. ex: {}", exception.toString()); + return Response.status(exception.getHttpCode()) + .entity(exception.toXml()).build(); + } +} diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java new file mode 100644 index 0000000000..b504bf4ea8 --- /dev/null +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java @@ -0,0 +1,81 @@ +/* + * 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.exception; + + +import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; +import static java.net.HttpURLConnection.HTTP_NOT_FOUND; + +/** + * This class represents errors from Ozone S3 service. + * This class needs to be updated to add new errors when required. + */ +public final class S3ErrorTable { + + private S3ErrorTable() { + //No one should construct this object. + } + + public static final OS3Exception INVALID_URI = new OS3Exception("InvalidURI", + "Couldn't parse the specified URI.", HTTP_BAD_REQUEST); + + public static final OS3Exception NO_SUCH_BUCKET = new OS3Exception( + "NoSuchBucket", "The specified bucket does not exist", HTTP_NOT_FOUND); + + + /** + * Create a new instance of Error. + * @param e Error Template + * @param requestID + * @param resource Resource associated with this exception + * @return creates a new instance of error based on the template + */ + public static OS3Exception newError(OS3Exception e, String requestID, + Resource resource){ + OS3Exception err = new OS3Exception(e.getCode(), e.getErrorMessage(), + e.getHttpCode()); + err.setRequestId(requestID); + err.setResource(resource.getResource()); + return err; + } + + /** + * Resources, which can be defined in OS3Exception. + */ + public enum Resource { + BUCKET("Bucket"); + + private final String resource; + + /** + * Constructs resource. + * @param value + */ + Resource(String value) { + this.resource = value; + } + + /** + * Get resource. + * @return string + */ + public String getResource() { + return this.resource; + } + } +} diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/package-info.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/package-info.java new file mode 100644 index 0000000000..d295ae885a --- /dev/null +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/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. + */ + +/** + * This package contains Ozone S3 exceptions. + */ diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/object/ListObject.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/object/ListObject.java index 73f1343791..a7bd7ad6f3 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/object/ListObject.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/object/ListObject.java @@ -37,6 +37,7 @@ import org.apache.hadoop.ozone.s3.commontypes.KeyMetadata; import org.apache.commons.lang3.StringUtils; +import org.apache.hadoop.ozone.s3.exception.OS3Exception; /** * List Object Rest endpoint. @@ -55,7 +56,7 @@ public ListObjectResponse get( @QueryParam("marker") String marker, @DefaultValue("1000") @QueryParam("max-keys") int maxKeys, @QueryParam("prefix") String prefix, - @Context HttpHeaders hh) throws IOException { + @Context HttpHeaders hh) throws OS3Exception, IOException { if (delimiter == null) { delimiter = "/"; diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/bucket/TestGetBucket.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/bucket/TestGetBucket.java index f5a6f7e2df..123dd7946d 100644 --- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/bucket/TestGetBucket.java +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/bucket/TestGetBucket.java @@ -24,6 +24,7 @@ import org.apache.hadoop.ozone.client.OzoneBucket; import org.apache.hadoop.ozone.client.OzoneClient; import org.apache.hadoop.ozone.client.OzoneClientStub; +import org.apache.hadoop.ozone.s3.exception.OS3Exception; import org.apache.hadoop.ozone.s3.object.ListObject; import org.apache.hadoop.ozone.s3.object.ListObjectResponse; @@ -36,7 +37,7 @@ public class TestGetBucket { @Test - public void listRoot() throws IOException { + public void listRoot() throws OS3Exception, IOException { ListObject getBucket = new ListObject(); @@ -58,7 +59,7 @@ public void listRoot() throws IOException { } @Test - public void listDir() throws IOException { + public void listDir() throws OS3Exception, IOException { ListObject getBucket = new ListObject(); @@ -78,7 +79,7 @@ public void listDir() throws IOException { } @Test - public void listSubDir() throws IOException { + public void listSubDir() throws OS3Exception, IOException { ListObject getBucket = new ListObject(); OzoneClient ozoneClient = diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/exception/TestOS3Exception.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/exception/TestOS3Exception.java new file mode 100644 index 0000000000..72e2949247 --- /dev/null +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/exception/TestOS3Exception.java @@ -0,0 +1,49 @@ +/* + * 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.exception; + +import org.apache.hadoop.ozone.web.utils.OzoneUtils; +import org.junit.Assert; +import org.junit.Test; + +/** + * This class tests OS3Exception class. + */ +public class TestOS3Exception { + + @Test + public void testOS3Exception() { + OS3Exception ex = new OS3Exception("AccessDenied", "Access Denied", + 403); + String requestId = OzoneUtils.getRequestID(); + ex = S3ErrorTable.newError(ex, requestId, S3ErrorTable.Resource.BUCKET); + String val = ex.toXml(); + String formatString = "\n" + + "\n" + + " %s\n" + + " %s\n" + + " %s\n" + + " %s\n" + + "\n"; + String expected = String.format(formatString, ex.getCode(), + ex.getErrorMessage(), ex.getResource(), + ex.getRequestId()); + Assert.assertEquals(expected, val); + } +} diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/exception/package-info.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/exception/package-info.java new file mode 100644 index 0000000000..31effe4fba --- /dev/null +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/exception/package-info.java @@ -0,0 +1,22 @@ +/* + * 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. + */ + +/** + * This package tests OS3Exception. + */ +package org.apache.hadoop.ozone.s3.exception; \ No newline at end of file