diff --git a/hadoop-hdfs-project/hadoop-hdfs/CHANGES-HDFS-7240.txt b/hadoop-hdfs-project/hadoop-hdfs/CHANGES-HDFS-7240.txt index 0ca3f687538..c300eef1552 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/CHANGES-HDFS-7240.txt +++ b/hadoop-hdfs-project/hadoop-hdfs/CHANGES-HDFS-7240.txt @@ -17,3 +17,6 @@ HDFS-8634. OzoneHandler: Add userAuth Interface and Simple userAuth handler. (Anu Engineer via Arpit Agarwal) + HDFS-8644. OzoneHandler : Add volume handler. (Anu Engineer via + Arpit Agarwal) + diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java new file mode 100644 index 00000000000..cf9fad62184 --- /dev/null +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java @@ -0,0 +1,37 @@ +/** + * 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; + +import org.apache.hadoop.classification.InterfaceAudience; + +/** + * This class contains constants for configuration keys used in Ozone. + */ +@InterfaceAudience.Private +public final class OzoneConfigKeys { + public static final String DFS_STORAGE_LOCAL_ROOT = + "dfs.ozone.localstorage.root"; + public static final String DFS_STORAGE_LOCAL_ROOT_DEFAULT = "/tmp/ozone"; + + /** + * There is no need to instantiate this class. + */ + private OzoneConfigKeys() { + } +} diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/StorageContainerConfiguration.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/StorageContainerConfiguration.java new file mode 100644 index 00000000000..a83fa339f57 --- /dev/null +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/StorageContainerConfiguration.java @@ -0,0 +1,35 @@ +/** + * 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; + +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.conf.Configuration; + +/** + * Configuration for ozone. + */ +@InterfaceAudience.Private +public class StorageContainerConfiguration extends Configuration { + static { + // adds the default resources + Configuration.addDefaultResource("hdfs-default.xml"); + Configuration.addDefaultResource("hdfs-site.xml"); + Configuration.addDefaultResource("ozone-site.xml"); + } +} \ No newline at end of file diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/exceptions/ErrorTable.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/exceptions/ErrorTable.java index be49d0486b4..a51dac5f373 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/exceptions/ErrorTable.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/exceptions/ErrorTable.java @@ -18,6 +18,7 @@ package org.apache.hadoop.ozone.web.exceptions; +import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.ozone.web.handlers.UserArgs; import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; @@ -33,6 +34,7 @@ import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; * Please note : The errors in this table are sorted by the HTTP_ERROR codes * if you add new error codes to this table please follow the same convention. */ +@InterfaceAudience.Private public final class ErrorTable { /* Error 400 */ @@ -149,12 +151,12 @@ public final class ErrorTable { * * @return creates a new instance of error based on the template */ - public static OzoneException newError(OzoneException e, long requestID, + public static OzoneException newError(OzoneException e, String requestID, String resource, String hostID) { OzoneException err = new OzoneException(e.getHttpCode(), e.getShortMessage(), e.getMessage()); - err.setRequestId(Long.toString(requestID)); + err.setRequestId(requestID); err.setResource(resource); err.setHostID(hostID); return err; @@ -172,7 +174,7 @@ public final class ErrorTable { OzoneException err = new OzoneException(e.getHttpCode(), e.getShortMessage(), e.getMessage()); - err.setRequestId(Long.toString(args.getRequestID())); + err.setRequestId(args.getRequestID()); err.setResource(args.getResourceName()); err.setHostID(args.getHostName()); return err; @@ -191,7 +193,7 @@ public final class ErrorTable { Exception ex) { OzoneException err = new OzoneException(e.getHttpCode(), e.getShortMessage(), ex); - err.setRequestId(Long.toString(args.getRequestID())); + err.setRequestId(args.getRequestID()); err.setResource(args.getResourceName()); err.setHostID(args.getHostName()); err.setMessage(ex.getMessage()); diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/exceptions/OzoneException.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/exceptions/OzoneException.java index e4a2b2ffc3c..576966ff643 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/exceptions/OzoneException.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/exceptions/OzoneException.java @@ -19,6 +19,7 @@ package org.apache.hadoop.ozone.web.exceptions; +import org.apache.hadoop.classification.InterfaceAudience; import org.codehaus.jackson.annotate.JsonAutoDetect; import org.codehaus.jackson.annotate.JsonProperty; import org.codehaus.jackson.map.ObjectMapper; @@ -29,6 +30,7 @@ import java.io.IOException; * Class the represents various errors returned by the * Ozone Layer. */ +@InterfaceAudience.Private public class OzoneException extends Exception { @JsonProperty("httpCode") diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/handlers/StorageHandlerBuilder.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/handlers/StorageHandlerBuilder.java new file mode 100644 index 00000000000..37c27daf27d --- /dev/null +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/handlers/StorageHandlerBuilder.java @@ -0,0 +1,73 @@ +/* + * 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.web.handlers; + +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.ozone.web.interfaces.StorageHandler; +import org.apache.hadoop.ozone.web.localstorage.LocalStorageHandler; + +/** + * This class is responsible for providing a {@link StorageHandler} + * implementation to object store web handlers. + */ +@InterfaceAudience.Private +public final class StorageHandlerBuilder { + + private static final ThreadLocal + STORAGE_HANDLER_THREAD_LOCAL = new ThreadLocal<>(); + + /** + * Returns the configured StorageHandler from thread-local storage for this + * thread. + * + * @return StorageHandler from thread-local storage + */ + public static StorageHandler getStorageHandler() { + StorageHandler storageHandler = STORAGE_HANDLER_THREAD_LOCAL.get(); + if (storageHandler != null) { + return storageHandler; + } else { + // This only happens while using mvn jetty:run for testing. + return new LocalStorageHandler(); + } + } + + /** + * Removes the configured StorageHandler from thread-local storage for this + * thread. + */ + public static void removeStorageHandler() { + STORAGE_HANDLER_THREAD_LOCAL.remove(); + } + + /** + * Sets the configured StorageHandler in thread-local storage for this thread. + * + * @param storageHandler StorageHandler to set in thread-local storage + */ + public static void setStorageHandler(StorageHandler storageHandler) { + STORAGE_HANDLER_THREAD_LOCAL.set(storageHandler); + } + + /** + * There is no reason to instantiate this class. + */ + private StorageHandlerBuilder() { + } +} diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/handlers/UserArgs.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/handlers/UserArgs.java index b80de3c2232..b83303ef060 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/handlers/UserArgs.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/handlers/UserArgs.java @@ -17,6 +17,8 @@ */ package org.apache.hadoop.ozone.web.handlers; +import org.apache.hadoop.classification.InterfaceAudience; + import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Request; import javax.ws.rs.core.UriInfo; @@ -25,9 +27,10 @@ import javax.ws.rs.core.UriInfo; * UserArgs is used to package caller info * and pass it down to file system. */ +@InterfaceAudience.Private public class UserArgs { private String userName; - private final long requestID; + private final String requestID; private final String hostName; private final UriInfo uri; private final Request request; @@ -38,14 +41,14 @@ public class UserArgs { * Constructs user args. * * @param userName - User name - * @param requestID _ Request ID + * @param requestID - Request ID * @param hostName - Host Name - * @param req - Request + * @param req - Request * @param info - Uri Info * @param httpHeaders - http headers */ - public UserArgs(String userName, long requestID, String hostName, Request req, - UriInfo info, HttpHeaders httpHeaders) { + public UserArgs(String userName, String requestID, String hostName, + Request req, UriInfo info, HttpHeaders httpHeaders) { this.hostName = hostName; this.userName = userName; this.requestID = requestID; @@ -54,6 +57,24 @@ public class UserArgs { this.headers = httpHeaders; } + /** + * Constructs user args when we don't know the user name yet. + * + * @param requestID _ Request ID + * @param hostName - Host Name + * @param req - Request + * @param info - UriInfo + * @param httpHeaders - http headers + */ + public UserArgs(String requestID, String hostName, Request req, UriInfo info, + HttpHeaders httpHeaders) { + this.hostName = hostName; + this.requestID = requestID; + this.uri = info; + this.request = req; + this.headers = httpHeaders; + } + /** * Returns hostname. * @@ -68,7 +89,7 @@ public class UserArgs { * * @return Long */ - public long getRequestID() { + public String getRequestID() { return requestID; } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/handlers/UserHandlerBuilder.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/handlers/UserHandlerBuilder.java new file mode 100644 index 00000000000..d9051f3449e --- /dev/null +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/handlers/UserHandlerBuilder.java @@ -0,0 +1,75 @@ +/* + * 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.web.handlers; + +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.ozone.web.interfaces.UserAuth; +import org.apache.hadoop.ozone.web.userauth.Simple; + +/** + * This class is responsible for providing a + * {@link org.apache.hadoop.ozone.web.interfaces.UserAuth} + * implementation to object store web handlers. + */ +@InterfaceAudience.Private +public final class UserHandlerBuilder { + + private static final ThreadLocal USER_AUTH_THREAD_LOCAL = + new ThreadLocal(); + + /** + * Returns the configured UserAuth from thread-local storage for this + * thread. + * + * @return UserAuth from thread-local storage + */ + public static UserAuth getAuthHandler() { + UserAuth authHandler = USER_AUTH_THREAD_LOCAL.get(); + if (authHandler != null) { + return authHandler; + } else { + // This only happens while using mvn jetty:run for testing. + return new Simple(); + } + } + + /** + * Removes the configured UserAuth from thread-local storage for this + * thread. + */ + public static void removeAuthHandler() { + USER_AUTH_THREAD_LOCAL.remove(); + } + + /** + * Sets the configured UserAuthHandler in thread-local storage for this + * thread. + * + * @param authHandler authHandler to set in thread-local storage + */ + public static void setAuthHandler(UserAuth authHandler) { + USER_AUTH_THREAD_LOCAL.set(authHandler); + } + + /** + * There is no reason to instantiate this class. + */ + private UserHandlerBuilder() { + } +} diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/handlers/VolumeArgs.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/handlers/VolumeArgs.java index 5bb62551b57..def1f0e921d 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/handlers/VolumeArgs.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/handlers/VolumeArgs.java @@ -18,6 +18,7 @@ package org.apache.hadoop.ozone.web.handlers; +import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.ozone.web.request.OzoneQuota; import javax.ws.rs.core.HttpHeaders; @@ -29,6 +30,7 @@ import javax.ws.rs.core.UriInfo; * related arguments in the call to underlying * file system. */ +@InterfaceAudience.Private public class VolumeArgs extends UserArgs { private String adminName; private final String volumeName; @@ -63,13 +65,32 @@ public class VolumeArgs extends UserArgs { * @param info - URI info * @param headers - http headers */ - public VolumeArgs(String userName, String volumeName, long requestID, + public VolumeArgs(String userName, String volumeName, String requestID, String hostName, Request request, UriInfo info, HttpHeaders headers) { super(userName, requestID, hostName, request, info, headers); this.volumeName = volumeName; } + /** + * Constructs volume Args. + * + * @param volumeName - volume Name + * @param userArgs - userArgs + */ + public VolumeArgs(String volumeName, UserArgs userArgs) { + this(userArgs.getUserName(), volumeName, userArgs.getRequestID(), + userArgs.getHostName(), userArgs.getRequest(), userArgs.getUri(), + userArgs.getHeaders()); + } + + /** + * Creates VolumeArgs from another VolumeArgs. + */ + public VolumeArgs(VolumeArgs volArgs) { + this(volArgs.getVolumeName(), volArgs); + } + /** * Sets Quota information. * diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/handlers/VolumeHandler.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/handlers/VolumeHandler.java new file mode 100644 index 00000000000..73a7a07e611 --- /dev/null +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/handlers/VolumeHandler.java @@ -0,0 +1,228 @@ +/* + * 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.web.handlers; + +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.ozone.web.exceptions.ErrorTable; +import org.apache.hadoop.ozone.web.exceptions.OzoneException; +import org.apache.hadoop.ozone.web.headers.Header; +import org.apache.hadoop.ozone.web.interfaces.StorageHandler; +import org.apache.hadoop.ozone.web.interfaces.UserAuth; +import org.apache.hadoop.ozone.web.interfaces.Volume; +import org.apache.hadoop.ozone.web.utils.OzoneUtils; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; +import java.io.IOException; + +import static java.net.HttpURLConnection.HTTP_CREATED; +import static java.net.HttpURLConnection.HTTP_OK; + +/** + * VolumeHandler handles volume specific HTTP calls. + * + * Most functions in this file follow a simple pattern. + * All calls are handled by VolumeProcessTemplate.handleCall, which + * calls back into doProcess function. + * + * Everything common to volume handling is abstracted out in handleCall function + * For Example : Checking that volume name is sane, we have a supported + * ozone version number and a valid date. That is everything common is in + * handleCall and actions specific to a call is inside doProcess callback. + */ +@InterfaceAudience.Private +public class VolumeHandler implements Volume { + /** + * Creates a volume. + * + * @param volume Volume Name, this has to be unique at Ozone cluster level + * @param quota Quota for this Storage Volume - () + * @param req Request Object + * @param uriInfo URI info + * @param headers Http Headers + * + * @return Standard JAX-RS Response + * + * @throws OzoneException + */ + @Override + public Response createVolume(String volume, final String quota, Request req, + UriInfo uriInfo, HttpHeaders headers) + throws OzoneException { + return new VolumeProcessTemplate() { + @Override + public Response doProcess(VolumeArgs args) + throws IOException, OzoneException { + UserAuth auth = UserHandlerBuilder.getAuthHandler(); + if (auth.isAdmin(args)) { + args.setAdminName(args.getUserName()); + String volumeOwner = auth.getOzoneUser(args); + + if (volumeOwner == null) { + throw ErrorTable.newError(ErrorTable.USER_NOT_FOUND, args); + } + + if (!auth.isUser(volumeOwner, args)) { + throw ErrorTable.newError(ErrorTable.USER_NOT_FOUND, args); + } + + args.setUserName(volumeOwner); + if (!quota.equals(Header.OZONE_QUOTA_UNDEFINED)) { + setQuotaArgs(args, quota); + } + StorageHandler fs = StorageHandlerBuilder.getStorageHandler(); + fs.createVolume(args); + return OzoneUtils.getResponse(args, HTTP_CREATED, ""); + } else { + throw ErrorTable.newError(ErrorTable.ACCESS_DENIED, args); + } + } + }.handleCall(volume, req, uriInfo, headers); + } + + /** + * Updates volume metadata. + * + * There are only two actions possible currently with updateVolume. + * Change the volume ownership or update quota. if you make a call + * with neither of these actions, update just returns 200 OK. + * + * @param volume Volume Name, this has to be unique at Ozone Level + * @param quota Quota for this volume - ()|remove + * @param req - Request Object + * @param uriInfo - URI info + * @param headers Http Headers + * + * @return Standard JAX-RS Response + * + * @throws OzoneException + */ + @Override + public Response updateVolume(String volume, final String quota, Request req, + UriInfo uriInfo, HttpHeaders headers) + throws OzoneException { + return new VolumeProcessTemplate() { + @Override + public Response doProcess(VolumeArgs args) + throws IOException, OzoneException { + UserAuth auth = UserHandlerBuilder.getAuthHandler(); + if (auth.isAdmin(args)) { + StorageHandler fs = StorageHandlerBuilder.getStorageHandler(); + args.setAdminName(args.getUserName()); + String newVolumeOwner = auth.getOzoneUser(args); + + if (newVolumeOwner != null) { + if (!auth.isUser(newVolumeOwner, args)) { + throw ErrorTable.newError(ErrorTable.USER_NOT_FOUND, args); + } + args.setUserName(newVolumeOwner); + fs.setVolumeOwner(args); + } + + if (!quota.equals(Header.OZONE_QUOTA_UNDEFINED)) { + if (quota.equals(Header.OZONE_QUOTA_REMOVE)) { + // if it is remove, just tell the file system to remove quota + fs.setVolumeQuota(args, true); + } else { + setQuotaArgs(args, quota); + fs.setVolumeQuota(args, false); + } + } + return OzoneUtils.getResponse(args, HTTP_OK, ""); + } else { + // Only Admins are allowed to update volumes + throw ErrorTable.newError(ErrorTable.ACCESS_DENIED, args); + } + } + }.handleCall(volume, req, uriInfo, headers); + } + + + /** + * Deletes a volume if it is empty. + * + * @param volume Volume Name + * @param req - Http Request + * @param uriInfo - http URI + * @param headers - http headers + * + * @return Standard JAX-RS Response + * + * @throws OzoneException + */ + @Override + public Response deleteVolume(String volume, Request req, UriInfo uriInfo, + HttpHeaders headers) throws OzoneException { + return new VolumeProcessTemplate() { + @Override + public Response doProcess(VolumeArgs args) + throws IOException, OzoneException { + UserAuth auth = UserHandlerBuilder.getAuthHandler(); + if (auth.isAdmin(args)) { + StorageHandler fs = StorageHandlerBuilder.getStorageHandler(); + fs.deleteVolume(args); + return OzoneUtils.getResponse(args, HTTP_OK, ""); + } else { + throw ErrorTable.newError(ErrorTable.ACCESS_DENIED, args); + } + } + }.handleCall(volume, req, uriInfo, headers); + } + + /** + * Returns Volume info. This API can be invoked either + * by admin or the owner + * + * @param volume - Storage Volume Name + * @param req - Http Req + * @param uriInfo - http URI + * @param headers - Http headers @return - Response + * + * @throws OzoneException + */ + @Override + public Response getVolumeInfo(String volume, final String info, Request req, + final UriInfo uriInfo, HttpHeaders headers) + throws OzoneException { + return new VolumeProcessTemplate() { + @Override + public Response doProcess(VolumeArgs args) + throws IOException, OzoneException { + + switch (info) { + case Header.OZONE_LIST_QUERY_BUCKET: + // TODO : Resolve this dependency when we bring + // in bucket code. + // return getBucketsInVolume(args); // Return list of Buckets + case Header.OZONE_LIST_QUERY_VOLUME: + return getVolumeInfoResponse(args); // Return volume info + case Header.OZONE_LIST_QUERY_SERVICE: + return getVolumesByUser(args); // Return list of volumes + default: + OzoneException ozoneException = + ErrorTable.newError(ErrorTable.INVALID_QUERY_PARAM, args); + ozoneException.setMessage("Unrecognized query param : " + info); + throw ozoneException; + } + } + }.handleCall(volume, req, uriInfo, headers); + } +} diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/handlers/VolumeProcessTemplate.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/handlers/VolumeProcessTemplate.java new file mode 100644 index 00000000000..8f4d4a66196 --- /dev/null +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/handlers/VolumeProcessTemplate.java @@ -0,0 +1,235 @@ +/* + * 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.web.handlers; + +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.ozone.web.exceptions.ErrorTable; +import org.apache.hadoop.ozone.web.exceptions.OzoneException; +import org.apache.hadoop.ozone.web.interfaces.StorageHandler; +import org.apache.hadoop.ozone.web.interfaces.UserAuth; +import org.apache.hadoop.ozone.web.response.ListVolumes; +import org.apache.hadoop.ozone.web.response.VolumeInfo; +import org.apache.hadoop.ozone.web.utils.OzoneUtils; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; +import java.io.IOException; +import java.nio.file.DirectoryNotEmptyException; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.NoSuchFileException; + +import static java.net.HttpURLConnection.HTTP_OK; + +/** + * This class abstracts way the repetitive tasks in + * handling volume related code. + */ +@InterfaceAudience.Private +public abstract class VolumeProcessTemplate { + + + /** + * The handle call is the common functionality for Volume + * handling code. + * + * @param volume - Name of the Volume + * @param request - request + * @param info - UriInfo + * @param headers - Http Headers + * + * @return Response + * + * @throws OzoneException + */ + public Response handleCall(String volume, Request request, UriInfo info, + HttpHeaders headers) throws OzoneException { + String reqID = OzoneUtils.getRequestID(); + String hostName = OzoneUtils.getHostName(); + try { + + OzoneUtils.validate(request, headers, reqID, volume, hostName); + + // we use the same logic for both bucket and volume names + OzoneUtils.verifyBucketName(volume); + UserAuth auth = UserHandlerBuilder.getAuthHandler(); + UserArgs userArgs = new UserArgs(reqID, hostName, request, info, headers); + + userArgs.setUserName(auth.getUser(userArgs)); + VolumeArgs args = new VolumeArgs(volume, userArgs); + + return doProcess(args); + } catch (IllegalArgumentException ex) { + OzoneException exp = ErrorTable + .newError(ErrorTable.INVALID_VOLUME_NAME, reqID, volume, hostName); + exp.setMessage(ex.getMessage()); + throw exp; + } catch (IOException ex) { + handleIOException(volume, reqID, hostName, ex); + } + return null; + } + + /** + * Specific handler for each call. + * + * @param args - Volume Args + * + * @return - Response + * + * @throws IOException + * @throws OzoneException + */ + public abstract Response doProcess(VolumeArgs args) + throws IOException, OzoneException; + + /** + * Maps Java File System Exceptions to Ozone Exceptions in the Volume path. + * + * @param volume - Name of the Volume + * @param reqID - Request ID + * @param hostName - HostName + * @param fsExp - Exception + * + * @throws OzoneException + */ + private void handleIOException(String volume, String reqID, String hostName, + IOException fsExp) throws OzoneException { + OzoneException exp = null; + + if (fsExp instanceof FileAlreadyExistsException) { + exp = ErrorTable + .newError(ErrorTable.VOLUME_ALREADY_EXISTS, reqID, volume, hostName); + } + + if (fsExp instanceof DirectoryNotEmptyException) { + exp = ErrorTable + .newError(ErrorTable.VOLUME_NOT_EMPTY, reqID, volume, hostName); + } + + if (fsExp instanceof NoSuchFileException) { + exp = ErrorTable + .newError(ErrorTable.INVALID_VOLUME_NAME, reqID, volume, hostName); + } + + if ((fsExp != null) && (exp != null)) { + exp.setMessage(fsExp.getMessage()); + } + + // We don't handle that FS error yet, report a Server Internal Error + if (exp == null) { + exp = + ErrorTable.newError(ErrorTable.SERVER_ERROR, reqID, volume, hostName); + if (fsExp != null) { + exp.setMessage(fsExp.getMessage()); + } + } + throw exp; + } + + /** + * Set the user provided string into args and throw ozone exception + * if needed. + * + * @param args - volume args + * @param quota - quota sting + * + * @throws OzoneException + */ + void setQuotaArgs(VolumeArgs args, String quota) throws OzoneException { + try { + args.setQuota(quota); + } catch (IllegalArgumentException ex) { + throw ErrorTable.newError(ErrorTable.MALFORMED_QUOTA, args, ex); + } + } + + /** + * Wraps calls into volumeInfo data. + * + * @param args - volumeArgs + * + * @return - VolumeInfo + * + * @throws IOException + * @throws OzoneException + */ + Response getVolumeInfoResponse(VolumeArgs args) + throws IOException, OzoneException { + StorageHandler fs = StorageHandlerBuilder.getStorageHandler(); + VolumeInfo info = fs.getVolumeInfo(args); + return OzoneUtils.getResponse(args, HTTP_OK, info.toJsonString()); + } + + + /** + * Returns all the volumes belonging to a user. + * + * @param user - userArgs + * + * @return - Response + * + * @throws OzoneException + * @throws IOException + */ + Response getVolumesByUser(UserArgs user) throws OzoneException, IOException { + StorageHandler fs = StorageHandlerBuilder.getStorageHandler(); + ListVolumes volumes = fs.listVolumes(user); + return OzoneUtils.getResponse(user, HTTP_OK, volumes.toJsonString()); + } + + + /** + * This call can also be invoked by Admins of the system where they can + * get the list of buckets of any user. + * + * User makes a call like + * GET / HTTP/1.1 + * Host: ozone.self + * + * @param args - volumeArgs + * + * @return Response - A list of buckets owned this user + * + * @throws OzoneException + */ + Response getVolumesByUser(VolumeArgs args) throws OzoneException { + String validatedUser = args.getUserName(); + try { + UserAuth auth = UserHandlerBuilder.getAuthHandler(); + if (auth.isAdmin(args)) { + validatedUser = auth.getOzoneUser(args); + if (validatedUser == null) { + validatedUser = auth.getUser(args); + } + } + + UserArgs user = + new UserArgs(validatedUser, args.getRequestID(), args.getHostName(), + args.getRequest(), args.getUri(), args.getHeaders()); + return getVolumesByUser(user); + } catch (IOException ex) { + OzoneException exp = ErrorTable.newError(ErrorTable.SERVER_ERROR, args); + exp.setMessage("unable to get the volume list for the user"); + throw exp; + } + } + +} diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/headers/Header.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/headers/Header.java index 53cf0f99c56..f217038b7e3 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/headers/Header.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/headers/Header.java @@ -18,9 +18,12 @@ package org.apache.hadoop.ozone.web.headers; +import org.apache.hadoop.classification.InterfaceAudience; + /** * OZONE specific HTTP headers. */ +@InterfaceAudience.Private public final class Header { public static final String OZONE_QUOTA_BYTES = "BYTES"; public static final String OZONE_QUOTA_MB = "MB"; @@ -33,6 +36,13 @@ public final class Header { public static final String OZONE_USER = "x-ozone-user"; public static final String OZONE_SIMPLE_AUTHENTICATION_SCHEME = "OZONE"; + public static final String OZONE_VERSION_HEADER = "x-ozone-version"; + + public static final String OZONE_LIST_QUERY_SERVICE = "service"; + public static final String OZONE_LIST_QUERY_VOLUME = "volume"; + + public static final String OZONE_REQUEST_ID = "x-ozone-request-id"; + public static final String OZONE_SERVER_NAME = "x-ozone-server-name"; private Header() { // Never constructed. diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/interfaces/StorageHandler.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/interfaces/StorageHandler.java index 2088409148f..043b59eea22 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/interfaces/StorageHandler.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/interfaces/StorageHandler.java @@ -18,6 +18,7 @@ package org.apache.hadoop.ozone.web.interfaces; +import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.ozone.web.exceptions.OzoneException; import org.apache.hadoop.ozone.web.handlers.UserArgs; import org.apache.hadoop.ozone.web.handlers.VolumeArgs; @@ -34,6 +35,7 @@ import java.io.IOException; * One for the local file system that is handy while testing * and another which will point to the HDFS backend. */ +@InterfaceAudience.Private public interface StorageHandler { /** diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/interfaces/UserAuth.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/interfaces/UserAuth.java index 1e2c3b32491..2ada01202e7 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/interfaces/UserAuth.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/interfaces/UserAuth.java @@ -18,6 +18,7 @@ package org.apache.hadoop.ozone.web.interfaces; +import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.ozone.web.exceptions.OzoneException; import org.apache.hadoop.ozone.web.handlers.UserArgs; @@ -26,6 +27,7 @@ import org.apache.hadoop.ozone.web.handlers.UserArgs; * * Please see concrete implementations for more information */ +@InterfaceAudience.Private public interface UserAuth { /** * Returns the user name as a string from the URI and HTTP headers. diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/interfaces/Volume.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/interfaces/Volume.java index 6ad742acb9d..db4bda637ea 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/interfaces/Volume.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/interfaces/Volume.java @@ -18,6 +18,7 @@ package org.apache.hadoop.ozone.web.interfaces; +import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.ozone.web.exceptions.OzoneException; import org.apache.hadoop.ozone.web.headers.Header; @@ -39,6 +40,7 @@ import javax.ws.rs.core.UriInfo; * Volume Interface acts as the HTTP entry point for * volume related functionality. */ +@InterfaceAudience.Private @Path("/{volume}") public interface Volume { diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/localstorage/LocalStorageHandler.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/localstorage/LocalStorageHandler.java new file mode 100644 index 00000000000..8655edc3dc1 --- /dev/null +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/localstorage/LocalStorageHandler.java @@ -0,0 +1,150 @@ +/* + * 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.web.localstorage; + +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.ozone.OzoneConfigKeys; +import org.apache.hadoop.ozone.StorageContainerConfiguration; +import org.apache.hadoop.ozone.web.exceptions.OzoneException; +import org.apache.hadoop.ozone.web.handlers.UserArgs; +import org.apache.hadoop.ozone.web.handlers.VolumeArgs; +import org.apache.hadoop.ozone.web.interfaces.StorageHandler; +import org.apache.hadoop.ozone.web.response.ListVolumes; +import org.apache.hadoop.ozone.web.response.VolumeInfo; + +import java.io.IOException; + + +/** + * PLEASE NOTE : This file is a dummy backend for test purposes + * and prototyping effort only. It does not handle any Object semantics + * correctly, neither does it take care of security. + */ +@InterfaceAudience.Private +public class LocalStorageHandler implements StorageHandler { + private String storageRoot = null; + + /** + * Constructs LocalStorageHandler. + */ + public LocalStorageHandler() { + StorageContainerConfiguration conf = new StorageContainerConfiguration(); + storageRoot = conf.getTrimmed( + OzoneConfigKeys.DFS_STORAGE_LOCAL_ROOT, + OzoneConfigKeys.DFS_STORAGE_LOCAL_ROOT_DEFAULT); + } + + /** + * Creates Storage Volume. + * + * @param args - volumeArgs + * + * @throws IOException + */ + @Override + public void createVolume(VolumeArgs args) throws IOException, OzoneException { + } + + /** + * setVolumeOwner - sets the owner of the volume. + * + * @param args volumeArgs + * + * @throws IOException + */ + @Override + public void setVolumeOwner(VolumeArgs args) + throws IOException, OzoneException { + } + + /** + * Set Volume Quota Info. + * + * @param args - volumeArgs + * @param remove - true if the request is to remove the quota + * + * @throws IOException + */ + @Override + public void setVolumeQuota(VolumeArgs args, boolean remove) + throws IOException, OzoneException { + } + + + /** + * Checks if a Volume exists and the user specified has access to the + * volume. + * + * @param args - volumeArgs + * + * @return - Boolean - True if the user can modify the volume. + * This is possible for owners of the volume and admin users + * + * @throws FileSystemException + */ + @Override + public boolean checkVolumeAccess(VolumeArgs args) + throws IOException, OzoneException { + return true; + } + + + /** + * Returns Info about the specified Volume. + * + * @param args - volumeArgs + * + * @return VolumeInfo + * + * @throws IOException + */ + @Override + public VolumeInfo getVolumeInfo(VolumeArgs args) + throws IOException, OzoneException { + return null; + } + + + /** + * Deletes an Empty Volume. + * + * @param args - Volume Args + * + * @throws IOException + */ + @Override + public void deleteVolume(VolumeArgs args) throws IOException, OzoneException { + } + + /** + * Returns the List of Volumes owned by the specific user. + * + * @param args - UserArgs + * + * @return - List of Volumes + * + * @throws IOException + */ + @Override + public ListVolumes listVolumes(UserArgs args) + throws IOException, OzoneException { + return null; + } + +} diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/request/OzoneQuota.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/request/OzoneQuota.java index f0f6b98bce1..ca6ddeb48f3 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/request/OzoneQuota.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/request/OzoneQuota.java @@ -19,6 +19,7 @@ package org.apache.hadoop.ozone.web.request; +import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.ozone.web.headers.Header; import org.codehaus.jackson.annotate.JsonIgnore; @@ -26,6 +27,7 @@ import org.codehaus.jackson.annotate.JsonIgnore; * represents an OzoneQuota Object that can be applied to * a storage volume. */ +@InterfaceAudience.Private public class OzoneQuota { private static final long MB_IN_BYTES = 1048576L; private static final long GB_IN_BYTES = 1073741824L; diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/response/ListVolumes.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/response/ListVolumes.java index 9c2ec9fd220..89cfebd3ff8 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/response/ListVolumes.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/response/ListVolumes.java @@ -17,6 +17,7 @@ */ package org.apache.hadoop.ozone.web.response; +import org.apache.hadoop.classification.InterfaceAudience; import org.codehaus.jackson.annotate.JsonAutoDetect; import org.codehaus.jackson.annotate.JsonMethod; import org.codehaus.jackson.map.ObjectMapper; @@ -35,6 +36,7 @@ import java.util.List; * List Volume Class is the class that is returned in JSON format to * users when they call ListVolumes. */ +@InterfaceAudience.Private public class ListVolumes { private List volumes; diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/response/VolumeInfo.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/response/VolumeInfo.java index e3ef53371db..57173689265 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/response/VolumeInfo.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/response/VolumeInfo.java @@ -19,6 +19,7 @@ package org.apache.hadoop.ozone.web.response; +import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.ozone.web.request.OzoneQuota; import org.codehaus.jackson.annotate.JsonAutoDetect; import org.codehaus.jackson.annotate.JsonMethod; @@ -35,6 +36,7 @@ import java.io.IOException; * VolumeInfo Class is the Java class that represents * Json when VolumeInfo Call is made. */ +@InterfaceAudience.Private public class VolumeInfo implements Comparable { static final String VOLUME_INFO = "VOLUME_INFO_FILTER"; diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/response/VolumeOwner.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/response/VolumeOwner.java index 02c4d048897..1dadf79079f 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/response/VolumeOwner.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/response/VolumeOwner.java @@ -19,6 +19,7 @@ package org.apache.hadoop.ozone.web.response; +import org.apache.hadoop.classification.InterfaceAudience; import org.codehaus.jackson.map.annotate.JsonSerialize; /** @@ -27,6 +28,7 @@ import org.codehaus.jackson.map.annotate.JsonSerialize; * This is a class instead of a string since we might need to extend this class * to support other forms of authentication. */ +@InterfaceAudience.Private public class VolumeOwner { @JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL) private String name; diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/userauth/Simple.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/userauth/Simple.java index 1c41cee2530..85b67fbae2e 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/userauth/Simple.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/userauth/Simple.java @@ -18,6 +18,7 @@ package org.apache.hadoop.ozone.web.userauth; +import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.ozone.web.exceptions.ErrorTable; import org.apache.hadoop.ozone.web.exceptions.OzoneException; import org.apache.hadoop.ozone.web.handlers.UserArgs; @@ -33,6 +34,7 @@ import java.util.List; * mode of ozone. This maps more or less to the simple user scheme in * HDFS. */ +@InterfaceAudience.Private public class Simple implements UserAuth { /** * Returns the x-ozone-user or the user on behalf of, This is diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/utils/OzoneConsts.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/utils/OzoneConsts.java index f54e5934be3..56c814ee12a 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/utils/OzoneConsts.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/utils/OzoneConsts.java @@ -18,13 +18,22 @@ package org.apache.hadoop.ozone.web.utils; +import org.apache.hadoop.classification.InterfaceAudience; + /** * Set of constants used in Ozone implementation. */ +@InterfaceAudience.Private public final class OzoneConsts { public static final String OZONE_SIMPLE_ROOT_USER = "root"; public static final String OZONE_SIMPLE_HDFS_USER = "hdfs"; + /* + * BucketName length is used for both buckets and volume lengths + */ + public static final int OZONE_MIN_BUCKET_NAME_LENGTH = 3; + public static final int OZONE_MAX_BUCKET_NAME_LENGTH = 63; + private OzoneConsts() { // Never Constructed } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/utils/OzoneUtils.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/utils/OzoneUtils.java new file mode 100644 index 00000000000..c454cc4553e --- /dev/null +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/ozone/web/utils/OzoneUtils.java @@ -0,0 +1,268 @@ +/* + * 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.web.utils; + +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.ozone.web.exceptions.ErrorTable; +import org.apache.hadoop.ozone.web.exceptions.OzoneException; +import org.apache.hadoop.ozone.web.handlers.UserArgs; +import org.apache.hadoop.ozone.web.headers.Header; +import org.apache.hadoop.util.Time; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.Response; +import java.io.InputStream; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.TimeZone; +import java.util.UUID; + +/** + * Set of Utility functions used in ozone. + */ +@InterfaceAudience.Private +public final class OzoneUtils { + + /** + * verifies that bucket name / volume name is a valid DNS name. + * + * @param bucketName Bucket Name to be validated + * + * @throws IllegalArgumentException + */ + public static void verifyBucketName(String bucketName) + throws IllegalArgumentException { + + if (bucketName == null) { + throw new IllegalArgumentException("Bucket or Volume name is null"); + } + + if ((bucketName.length() < OzoneConsts.OZONE_MIN_BUCKET_NAME_LENGTH) || + (bucketName.length() > OzoneConsts.OZONE_MAX_BUCKET_NAME_LENGTH)) { + throw new IllegalArgumentException( + "Bucket or Volume length is illegal, " + + "valid length is 3-63 characters"); + } + + if ((bucketName.charAt(0) == '.') || (bucketName.charAt(0) == '-')) { + throw new IllegalArgumentException( + "Bucket or Volume name cannot start with a period or dash"); + } + + if ((bucketName.charAt(bucketName.length() - 1) == '.') || + (bucketName.charAt(bucketName.length() - 1) == '-')) { + throw new IllegalArgumentException( + "Bucket or Volume name cannot end with a period or dash"); + } + + boolean isIPv4 = true; + char prev = (char) 0; + + for (int index = 0; index < bucketName.length(); index++) { + char currChar = bucketName.charAt(index); + + if (currChar != '.') { + isIPv4 = ((currChar >= '0') && (currChar <= '9')) && isIPv4; + } + + if (currChar > 'A' && currChar < 'Z') { + throw new IllegalArgumentException( + "Bucket or Volume name does not support uppercase characters"); + } + + + if ((currChar != '.') && (currChar != '-')) { + if ((currChar < '0') || (currChar > '9' && currChar < 'a') || + (currChar > 'z')) { + throw new IllegalArgumentException("Bucket or Volume name has an " + + "unsupported character : " + + currChar); + } + } + + if ((prev == '.') && (currChar == '.')) { + throw new IllegalArgumentException("Bucket or Volume name should not " + + "have two contiguous periods"); + } + + if ((prev == '-') && (currChar == '.')) { + throw new IllegalArgumentException( + "Bucket or Volume name should not have period after dash"); + } + + if ((prev == '.') && (currChar == '-')) { + throw new IllegalArgumentException( + "Bucket or Volume name should not have dash after period"); + } + prev = currChar; + } + + if (isIPv4) { + throw new IllegalArgumentException( + "Bucket or Volume name cannot be an IPv4 address or all numeric"); + } + } + + /** + * Returns a random Request ID. + * + * Request ID is returned to the client as well as flows thru the system + * facilitating debugging on why a certain request failed. + * + * @return String random request ID + */ + public static String getRequestID() { + return UUID.randomUUID().toString(); + } + + /** + * Return host name if possible. + * + * @return Host Name or localhost + */ + public static String getHostName() { + String host = "localhost"; + try { + host = InetAddress.getLocalHost().getHostName(); + } catch (UnknownHostException e) { + // Ignore the error + } + return host; + } + + /** + * Basic validate routine to make sure that all the + * required headers are in place. + * + * @param request - http request + * @param headers - http headers + * @param reqId - request id + * @param resource - Resource Name + * @param hostname - Hostname + * + * @throws OzoneException + */ + public static void validate(Request request, HttpHeaders headers, + String reqId, String resource, String hostname) + throws OzoneException { + + List ozHeader = + headers.getRequestHeader(Header.OZONE_VERSION_HEADER); + if (ozHeader == null) { + throw ErrorTable + .newError(ErrorTable.MISSING_VERSION, reqId, resource, hostname); + } + + List date = headers.getRequestHeader(HttpHeaders.DATE); + if (date == null) { + throw ErrorTable + .newError(ErrorTable.MISSING_DATE, reqId, resource, hostname); + } + + /* + TODO : + Ignore the results for time being. Eventually we can validate if the + request Date time is too skewed and reject if it is so. + */ + parseDate(date.get(0), reqId, resource, hostname); + + } + + /** + * Parses the Date String coming from the Users. + * + * @param dateString - Date String + * @param reqID - Ozone Request ID + * @param resource - Resource Name + * @param hostname - HostName + * + * @return - Date + * + * @throws OzoneException - in case of parsing error + */ + public static synchronized Date parseDate(String dateString, String reqID, + String resource, String hostname) + throws OzoneException { + SimpleDateFormat format = + new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); + format.setTimeZone(TimeZone.getTimeZone("GMT")); + + try { + return format.parse(dateString); + } catch (ParseException ex) { + OzoneException exp = + ErrorTable.newError(ErrorTable.BAD_DATE, reqID, resource, hostname); + exp.setMessage(ex.getMessage()); + throw exp; + } + } + + /** + * Returns a response with appropriate OZONE headers and payload. + * + * @param args - UserArgs or Inherited class + * @param statusCode - HttpStatus code + * @param payload - Content Body + * + * @return JAX-RS Response + */ + public static Response getResponse(UserArgs args, int statusCode, + String payload) { + SimpleDateFormat format = + new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); + format.setTimeZone(TimeZone.getTimeZone("GMT")); + String date = format.format(new Date(Time.monotonicNow())); + return Response.ok(payload) + .header(Header.OZONE_SERVER_NAME, args.getHostName()) + .header(Header.OZONE_REQUEST_ID, args.getRequestID()) + .header(HttpHeaders.DATE, date).status(statusCode).build(); + } + + /** + * Returns a response with appropriate OZONE headers and payload. + * + * @param args - UserArgs or Inherited class + * @param statusCode - HttpStatus code + * @param stream InputStream + * + * @return JAX-RS Response + */ + public static Response getResponse(UserArgs args, int statusCode, + InputStream stream) { + SimpleDateFormat format = + new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); + format.setTimeZone(TimeZone.getTimeZone("GMT")); + String date = format.format(new Date(Time.monotonicNow())); + return Response.ok(stream) + .header(Header.OZONE_SERVER_NAME, args.getHostName()) + .header(Header.OZONE_REQUEST_ID, args.getRequestID()) + .header(HttpHeaders.DATE, date).status(statusCode) + .header(HttpHeaders.CONTENT_TYPE, "application/octet-stream").build(); + } + + private OzoneUtils() { + // Never constructed + } +} diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/ozone/web/TestErrorCode.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/ozone/web/TestErrorCode.java new file mode 100644 index 00000000000..6639e4816d4 --- /dev/null +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/ozone/web/TestErrorCode.java @@ -0,0 +1,50 @@ +/* + * 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.web; + +import org.apache.hadoop.ozone.web.exceptions.ErrorTable; +import org.apache.hadoop.ozone.web.exceptions.OzoneException; +import org.junit.Test; + +import static junit.framework.TestCase.assertEquals; +import static org.apache.hadoop.ozone.web.utils.OzoneUtils.getRequestID; + +public class TestErrorCode { + /** + * Test Error Generator functions. + */ + @Test + public void TestErrorGen() { + OzoneException e = ErrorTable + .newError(ErrorTable.ACCESS_DENIED, getRequestID(), "/test/path", + "localhost"); + assertEquals(e.getHostID(), "localhost"); + assertEquals(e.getShortMessage(), + ErrorTable.ACCESS_DENIED.getShortMessage()); + } + + @Test + public void TestErrorGenWithException() { + OzoneException e = + new OzoneException(ErrorTable.ACCESS_DENIED.getHttpCode(), + "short message", new Exception("Hello")); + assertEquals("short message", e.getShortMessage()); + assertEquals("Hello", e.getMessage()); + } +} diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/ozone/web/TestUtils.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/ozone/web/TestUtils.java new file mode 100644 index 00000000000..f63e3698559 --- /dev/null +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/ozone/web/TestUtils.java @@ -0,0 +1,100 @@ +/* + * 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.web; + +import org.junit.Test; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +import static org.apache.hadoop.ozone.web.utils.OzoneUtils.getRequestID; +import static org.apache.hadoop.ozone.web.utils.OzoneUtils.verifyBucketName; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class TestUtils { + + /** + * Tests if the bucket name handling is correct. + */ + @Test + public void TestValidBucketNames() { + HashMap testMatrix; + // Init the Table with Strings and Expected Return values + testMatrix = new HashMap(); + + testMatrix.put("bucket-.ozone.self", Boolean.FALSE); + testMatrix.put("bucket.-ozone.self", Boolean.FALSE); + testMatrix.put(".bucket.ozone.self", Boolean.FALSE); + testMatrix.put("bucket.ozone.self.", Boolean.FALSE); + testMatrix.put("bucket..ozone.self", Boolean.FALSE); + testMatrix.put("192.1.1.1", Boolean.FALSE); + testMatrix.put("ab", Boolean.FALSE); + testMatrix.put("bucket.ozone.self.this.is.a.really.long.name.that." + + "is.more.than.sixty.three.characters.long.for.sure", + Boolean.FALSE); + testMatrix.put(null, Boolean.FALSE); + testMatrix.put("bucket@$", Boolean.FALSE); + testMatrix.put("BUCKET", Boolean.FALSE); + testMatrix.put("bucket .ozone.self", Boolean.FALSE); + testMatrix.put(" bucket.ozone.self", Boolean.FALSE); + testMatrix.put("bucket.ozone.self-", Boolean.FALSE); + testMatrix.put("-bucket.ozone.self", Boolean.FALSE); + + testMatrix.put("bucket", Boolean.TRUE); + testMatrix.put("bucket.ozone.self", Boolean.TRUE); + testMatrix.put("bucket.ozone.self", Boolean.TRUE); + testMatrix.put("bucket-name.ozone.self", Boolean.TRUE); + testMatrix.put("bucket.1.ozone.self", Boolean.TRUE); + + Set keys = testMatrix.keySet(); + for (String key : keys) { + if(testMatrix.get(key)) { + + // For valid names there should be no exceptions at all + verifyBucketName(key); + } else { + try { + verifyBucketName(key); + // should never get here since the isValid call will throw + fail("An exception was expected but did not happen."); + } catch(IllegalArgumentException e){ + + } + } + } + } + + /** + * Just calls Request ID many times and assert we + * got different values, ideally this should be + * run under parallel threads. Since the function under + * test has no external dependencies it is assumed + * that this test is good enough. + */ + @Test + public void TestRequestIDisRandom(){ + HashSet set = new HashSet<>(); + for (int i = 0; i < 1000; i ++){ + assertTrue(set.add(getRequestID())); + } + } + +}