/******************************************************************************* * Copyright (c) 2006-2011 Gluster, Inc. <http://www.gluster.com> * This file is part of Gluster Management Gateway. * * Gluster Management Gateway is free software; you can redistribute * it and/or modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * Gluster Management Gateway is distributed in the hope that it * will be useful, but WITHOUT ANY WARRANTY; without even the * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see * <http://www.gnu.org/licenses/>. *******************************************************************************/ package org.gluster.storage.management.gateway.resources.v1_0; import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_ACCESS_PROTOCOLS; import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_AUTO_COMMIT; import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_BRICKS; import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_CIFS_ENABLE; import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_CIFS_USERS; import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_FIX_LAYOUT; import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_FORCE; import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_FORCED_DATA_MIGRATE; import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_MIGRATE_DATA; import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_OPERATION; import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_REPLICA_COUNT; import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_SOURCE; import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_STRIPE_COUNT; import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_TARGET; import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_TRANSPORT_TYPE; import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_VOLUME_NAME; import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_VOLUME_OPTIONS; import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_VOLUME_TYPE; import static org.gluster.storage.management.core.constants.RESTConstants.PATH_PARAM_CLUSTER_NAME; import static org.gluster.storage.management.core.constants.RESTConstants.PATH_PARAM_VOLUME_NAME; import static org.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_BRICKS; import static org.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_BRICK_NAME; import static org.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_DELETE_OPTION; import static org.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_DOWNLOAD; import static org.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_FROM_TIMESTAMP; import static org.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_LINE_COUNT; import static org.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_LOG_SEVERITY; import static org.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_MAX_COUNT; import static org.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_NEXT_TO; import static org.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_TO_TIMESTAMP; import static org.gluster.storage.management.core.constants.RESTConstants.RESOURCE_BRICKS; import static org.gluster.storage.management.core.constants.RESTConstants.RESOURCE_DEFAULT_OPTIONS; import static org.gluster.storage.management.core.constants.RESTConstants.RESOURCE_DOWNLOAD; import static org.gluster.storage.management.core.constants.RESTConstants.RESOURCE_LOGS; import static org.gluster.storage.management.core.constants.RESTConstants.RESOURCE_OPTIONS; import static org.gluster.storage.management.core.constants.RESTConstants.RESOURCE_PATH_CLUSTERS; import static org.gluster.storage.management.core.constants.RESTConstants.RESOURCE_TASKS; import static org.gluster.storage.management.core.constants.RESTConstants.RESOURCE_VOLUMES; import java.io.File; import java.util.Arrays; import java.util.List; import javax.ws.rs.DELETE; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.apache.log4j.Logger; import org.gluster.storage.management.core.constants.RESTConstants; import org.gluster.storage.management.core.exceptions.GlusterRuntimeException; import org.gluster.storage.management.core.exceptions.GlusterValidationException; import org.gluster.storage.management.core.model.Volume; import org.gluster.storage.management.core.model.VolumeLogMessage; import org.gluster.storage.management.core.model.Volume.VOLUME_STATUS; import org.gluster.storage.management.core.model.Volume.VOLUME_TYPE; import org.gluster.storage.management.core.response.LogMessageListResponse; import org.gluster.storage.management.core.response.VolumeListResponse; import org.gluster.storage.management.core.response.VolumeOptionInfoListResponse; import org.gluster.storage.management.core.utils.FileUtil; import org.gluster.storage.management.gateway.services.ClusterService; import org.gluster.storage.management.gateway.services.VolumeService; import com.sun.jersey.api.core.InjectParam; import com.sun.jersey.spi.resource.Singleton; @Singleton @Path(RESOURCE_PATH_CLUSTERS + "/{" + PATH_PARAM_CLUSTER_NAME + "}/" + RESOURCE_VOLUMES) public class VolumesResource extends AbstractResource { private static final Logger logger = Logger.getLogger(VolumesResource.class); @InjectParam private ClusterService clusterService; @InjectParam private VolumeService volumeService; @GET @Produces({ MediaType.APPLICATION_XML }) public Response getVolumesXML(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, @QueryParam(QUERY_PARAM_MAX_COUNT) Integer maxCount, @QueryParam(QUERY_PARAM_NEXT_TO) String previousVolumeName) { return okResponse(new VolumeListResponse(volumeService.getVolumes(clusterName, maxCount, previousVolumeName)), MediaType.APPLICATION_XML); } @GET @Produces({ MediaType.APPLICATION_JSON }) public Response getVolumesJSON(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, @QueryParam(QUERY_PARAM_MAX_COUNT) Integer maxCount, @QueryParam(QUERY_PARAM_NEXT_TO) String previousVolumeName) { return okResponse(new VolumeListResponse(volumeService.getVolumes(clusterName, maxCount, previousVolumeName)), MediaType.APPLICATION_JSON); } @POST @Produces(MediaType.APPLICATION_XML) public Response createVolume(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, @FormParam(FORM_PARAM_VOLUME_NAME) String volumeName, @FormParam(FORM_PARAM_VOLUME_TYPE) String volumeType, @FormParam(FORM_PARAM_TRANSPORT_TYPE) String transportType, @FormParam(FORM_PARAM_REPLICA_COUNT) Integer replicaCount, @FormParam(FORM_PARAM_STRIPE_COUNT) Integer stripeCount, @FormParam(FORM_PARAM_BRICKS) String bricks, @FormParam(FORM_PARAM_ACCESS_PROTOCOLS) String accessProtocols, @FormParam(FORM_PARAM_VOLUME_OPTIONS) String options, @FormParam(FORM_PARAM_CIFS_USERS) String cifsUsers) { int count = 0; if (clusterName == null || clusterName.isEmpty()) { return badRequestResponse("Cluster name must not be empty!"); } String missingParam = checkMissingParamsForCreateVolume(volumeName, volumeType, transportType, bricks, accessProtocols, options); if (missingParam != null) { throw new GlusterValidationException("Parameter [" + missingParam + "] is missing in request!"); } // For missing parameter, let default value if (volumeType.equals(VOLUME_TYPE.REPLICATE.toString()) || volumeType.equals(VOLUME_TYPE.DISTRIBUTED_REPLICATE.toString())) { count = (replicaCount == null) ? Volume.DEFAULT_REPLICA_COUNT : replicaCount; } else if (volumeType.equals(VOLUME_TYPE.STRIPE.toString()) || volumeType.equals(VOLUME_TYPE.DISTRIBUTED_STRIPE.toString())) { count = (stripeCount == null) ? Volume.DEFAULT_STRIPE_COUNT : stripeCount; } volumeService.createVolume(clusterName, volumeName, volumeType, transportType, count, bricks, accessProtocols, options, cifsUsers); return createdResponse(volumeName); } /** * Returns name of the missing parameter if any. If all parameters are present, */ private String checkMissingParamsForCreateVolume(String volumeName, String volumeType, String transportType, String bricks, String accessProtocols, String options) { return (volumeName == null || volumeName.isEmpty()) ? FORM_PARAM_VOLUME_NAME : (volumeType == null || volumeType.isEmpty()) ? FORM_PARAM_VOLUME_TYPE : (transportType == null || transportType.isEmpty()) ? FORM_PARAM_TRANSPORT_TYPE : (bricks == null || bricks.isEmpty()) ? FORM_PARAM_BRICKS : (accessProtocols == null || accessProtocols.isEmpty()) ? FORM_PARAM_ACCESS_PROTOCOLS : (options == null || options.isEmpty()) ? FORM_PARAM_VOLUME_OPTIONS : null; } @GET @Path("{" + PATH_PARAM_VOLUME_NAME + "}") @Produces(MediaType.APPLICATION_XML) public Response getVolumeXML(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, @PathParam(PATH_PARAM_VOLUME_NAME) String volumeName) { return okResponse(volumeService.getVolume(clusterName, volumeName), MediaType.APPLICATION_XML); } @GET @Path("{" + PATH_PARAM_VOLUME_NAME + "}") @Produces(MediaType.APPLICATION_JSON) public Response getVolumeJSON(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, @PathParam(PATH_PARAM_VOLUME_NAME) String volumeName) { return okResponse(volumeService.getVolume(clusterName, volumeName), MediaType.APPLICATION_JSON); } @PUT @Path("{" + PATH_PARAM_VOLUME_NAME + "}") public Response performOperation(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, @PathParam(PATH_PARAM_VOLUME_NAME) String volumeName, @FormParam(FORM_PARAM_OPERATION) String operation, @FormParam(FORM_PARAM_FIX_LAYOUT) Boolean isFixLayout, @FormParam(FORM_PARAM_MIGRATE_DATA) Boolean isMigrateData, @FormParam(FORM_PARAM_FORCED_DATA_MIGRATE) Boolean isForcedDataMigrate, @FormParam(FORM_PARAM_CIFS_ENABLE) Boolean enableCifs, @FormParam(FORM_PARAM_CIFS_USERS) String cifsUsers, @FormParam(FORM_PARAM_BRICKS) String bricks, @FormParam(FORM_PARAM_FORCE) Boolean force) { if (clusterName == null || clusterName.isEmpty()) { throw new GlusterValidationException("Cluster name must not be empty!"); } if (volumeName == null || volumeName.isEmpty()) { throw new GlusterValidationException("Volume name must not be empty!"); } if (clusterService.getCluster(clusterName) == null) { throw new GlusterValidationException("Cluster [" + clusterName + "] not found!"); } try { if (operation.equals(RESTConstants.TASK_REBALANCE_START)) { String taskId = volumeService.rebalanceStart(clusterName, volumeName, isFixLayout, isMigrateData, isForcedDataMigrate); return acceptedResponse(RESTConstants.RESOURCE_PATH_CLUSTERS + "/" + clusterName + "/" + RESOURCE_TASKS + "/" + taskId); } else if (operation.equals(RESTConstants.TASK_REBALANCE_STOP)) { volumeService.rebalanceStop(clusterName, volumeName); } else if (operation.equals(RESTConstants.FORM_PARAM_CIFS_CONFIG)) { Volume newVolume = volumeService.getVolume(clusterName, volumeName); if (enableCifs) { // After add/modify volume cifs users, start/restart the cifs service volumeService.createCIFSUsers(clusterName, volumeName, cifsUsers); if (newVolume.getStatus() == VOLUME_STATUS.ONLINE) { volumeService.startCifsReExport(clusterName, volumeName); } } else { // Stop the Cifs service and delete the users (!important) if (newVolume.getStatus() == VOLUME_STATUS.ONLINE) { volumeService.stopCifsReExport(clusterName, volumeName); } volumeService.deleteCifsUsers(clusterName, volumeName); } } else if (operation.equals(RESTConstants.TASK_LOG_ROTATE)) { List<String> brickList = Arrays.asList(bricks.split(",")); volumeService.logRotate(clusterName, volumeName, brickList); } else { if (force == null) { force = false; } volumeService.performVolumeOperation(clusterName, volumeName, operation, force); } return noContentResponse(); } catch(Exception e) { return errorResponse(e.getMessage()); } } @DELETE @Path("{" + PATH_PARAM_VOLUME_NAME + "}") public Response deleteVolume(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, @PathParam(PATH_PARAM_VOLUME_NAME) String volumeName, @QueryParam(QUERY_PARAM_DELETE_OPTION) Boolean deleteFlag) { volumeService.deleteVolume(clusterName, volumeName, deleteFlag); return noContentResponse(); } @DELETE @Path("{" + PATH_PARAM_VOLUME_NAME + "}/" + RESOURCE_BRICKS) public Response removeBricks(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, @PathParam(PATH_PARAM_VOLUME_NAME) String volumeName, @QueryParam(QUERY_PARAM_BRICKS) String bricks, @QueryParam(QUERY_PARAM_DELETE_OPTION) Boolean deleteFlag) { volumeService.removeBricksFromVolume(clusterName, volumeName, bricks, deleteFlag); return noContentResponse(); } @POST @Path("{" + PATH_PARAM_VOLUME_NAME + " }/" + RESOURCE_OPTIONS) public Response setOption(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, @PathParam(PATH_PARAM_VOLUME_NAME) String volumeName, @FormParam(RESTConstants.FORM_PARAM_OPTION_KEY) String key, @FormParam(RESTConstants.FORM_PARAM_OPTION_VALUE) String value) { volumeService.setVolumeOption(clusterName, volumeName, key, value); return createdResponse(key); } @PUT @Path("{" + PATH_PARAM_VOLUME_NAME + " }/" + RESOURCE_OPTIONS) public Response resetOptions(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, @PathParam(PATH_PARAM_VOLUME_NAME) String volumeName) { volumeService.resetVolumeOptions(clusterName, volumeName); return noContentResponse(); } @GET @Path(RESOURCE_DEFAULT_OPTIONS) @Produces(MediaType.APPLICATION_XML) public VolumeOptionInfoListResponse getOptionsInfoXML(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName) { return volumeService.getVolumeOptionsInfo(clusterName); } @GET @Path(RESOURCE_DEFAULT_OPTIONS) @Produces(MediaType.APPLICATION_JSON) public VolumeOptionInfoListResponse getOptionsInfoJSON(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName) { return volumeService.getVolumeOptionsInfo(clusterName); } @GET @Produces(MediaType.APPLICATION_OCTET_STREAM) @Path("{" + PATH_PARAM_VOLUME_NAME + "}/" + RESOURCE_LOGS + "/" + RESOURCE_DOWNLOAD) public Response downloadLogs(@PathParam(PATH_PARAM_CLUSTER_NAME) final String clusterName, @PathParam(PATH_PARAM_VOLUME_NAME) final String volumeName) { if (clusterName == null || clusterName.isEmpty()) { return badRequestResponse("Cluster name must not be empty!"); } if (volumeName == null || volumeName.isEmpty()) { return badRequestResponse("Volume name must not be empty!"); } if (clusterService.getCluster(clusterName) == null) { return notFoundResponse("Cluster [" + clusterName + "] not found!"); } try { final Volume volume = volumeService.getVolume(clusterName, volumeName); File archiveFile = new File(volumeService.downloadLogs(volume)); byte[] data = FileUtil.readFileAsByteArray(archiveFile); archiveFile.delete(); return streamingOutputResponse(createStreamingOutput(data)); } catch (Exception e) { logger.error("Volume [" + volumeName + "] doesn't exist in cluster [" + clusterName + "]! [" + e.getStackTrace() + "]"); throw (GlusterRuntimeException) e; } } @GET @Path("{" + PATH_PARAM_VOLUME_NAME + "}/" + RESOURCE_LOGS) @Produces(MediaType.APPLICATION_XML) public Response getLogsXML(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, @PathParam(PATH_PARAM_VOLUME_NAME) String volumeName, @QueryParam(QUERY_PARAM_BRICK_NAME) String brickName, @QueryParam(QUERY_PARAM_LOG_SEVERITY) String severity, @QueryParam(QUERY_PARAM_FROM_TIMESTAMP) String fromTimestamp, @QueryParam(QUERY_PARAM_TO_TIMESTAMP) String toTimestamp, @QueryParam(QUERY_PARAM_LINE_COUNT) Integer lineCount, @QueryParam(QUERY_PARAM_DOWNLOAD) Boolean download) { return getLogs(clusterName, volumeName, brickName, severity, fromTimestamp, toTimestamp, lineCount, MediaType.APPLICATION_XML); } @GET @Path("{" + PATH_PARAM_VOLUME_NAME + "}/" + RESOURCE_LOGS) @Produces(MediaType.APPLICATION_JSON) public Response getLogsJSON(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, @PathParam(PATH_PARAM_VOLUME_NAME) String volumeName, @QueryParam(QUERY_PARAM_BRICK_NAME) String brickName, @QueryParam(QUERY_PARAM_LOG_SEVERITY) String severity, @QueryParam(QUERY_PARAM_FROM_TIMESTAMP) String fromTimestamp, @QueryParam(QUERY_PARAM_TO_TIMESTAMP) String toTimestamp, @QueryParam(QUERY_PARAM_LINE_COUNT) Integer lineCount, @QueryParam(QUERY_PARAM_DOWNLOAD) Boolean download) { return getLogs(clusterName, volumeName, brickName, severity, fromTimestamp, toTimestamp, lineCount, MediaType.APPLICATION_JSON); } private Response getLogs(String clusterName, String volumeName, String brickName, String severity, String fromTimestamp, String toTimestamp, Integer lineCount, String mediaType) { List<VolumeLogMessage> logMessages = volumeService.getLogs(clusterName, volumeName, brickName, severity, fromTimestamp, toTimestamp, lineCount); return okResponse(new LogMessageListResponse(logMessages), mediaType); } @POST @Path("{" + PATH_PARAM_VOLUME_NAME + "}/" + RESOURCE_BRICKS) public Response addBricks(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, @PathParam(PATH_PARAM_VOLUME_NAME) String volumeName, @FormParam(FORM_PARAM_BRICKS) String bricks) { volumeService.addBricksToVolume(clusterName, volumeName, bricks); return createdResponse(volumeName + "/" + RESOURCE_BRICKS); } @PUT @Path("{" + PATH_PARAM_VOLUME_NAME + "}/" + RESOURCE_BRICKS) public Response migrateBrick(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, @PathParam(PATH_PARAM_VOLUME_NAME) String volumeName, @FormParam(FORM_PARAM_SOURCE) String fromBrick, @FormParam(FORM_PARAM_TARGET) String toBrick, @FormParam(FORM_PARAM_AUTO_COMMIT) Boolean autoCommit) { String taskId = volumeService.migrateBrickStart(clusterName, volumeName, fromBrick, toBrick, autoCommit); return acceptedResponse(RESTConstants.RESOURCE_PATH_CLUSTERS + "/" + clusterName + "/" + RESOURCE_TASKS + "/" + taskId); } }