/*******************************************************************************
* 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.services;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.log4j.Logger;
import org.gluster.storage.management.core.constants.CoreConstants;
import org.gluster.storage.management.core.constants.GlusterConstants;
import org.gluster.storage.management.core.exceptions.GlusterRuntimeException;
import org.gluster.storage.management.core.model.Brick;
import org.gluster.storage.management.core.model.Status;
import org.gluster.storage.management.core.model.TaskStatus;
import org.gluster.storage.management.core.model.Volume;
import org.gluster.storage.management.core.model.Brick.BRICK_STATUS;
import org.gluster.storage.management.core.model.Volume.TRANSPORT_TYPE;
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.VolumeOptionInfoListResponse;
import org.gluster.storage.management.core.utils.StringUtil;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
/**
* Gluster Interface for GlusterFS version 3.2.3
*/
@Component
@Lazy(value=true)
public class Gluster323InterfaceService extends AbstractGlusterInterface {
private static final String VOLUME_NAME_PFX = "Volume Name:";
private static final String VOLUME_TYPE_PFX = "Type:";
private static final String VOLUME_STATUS_PFX = "Status:";
private static final String VOLUME_NUMBER_OF_BRICKS = "Number of Bricks:";
private static final String VOLUME_TRANSPORT_TYPE_PFX = "Transport-type:";
private static final String VOLUME_BRICKS_GROUP_PFX = "Bricks";
private static final String VOLUME_OPTIONS_RECONFIG_PFX = "Options Reconfigured";
private static final String VOLUME_LOG_LOCATION_PFX = "log file location:";
private static final String VOLUME_TYPE_DISTRIBUTE = "Distribute";
private static final String VOLUME_TYPE_REPLICATE = "Replicate";
private static final String VOLUME_TYPE_DISTRIBUTED_REPLICATTE = "Distributed-Replicate";
private static final String VOLUME_TYPE_STRIPE = "Stripe";
private static final String VOLUME_TYPE_DISTRIBUTED_STRIPE = "Distributed-Stripe";
private static final String BRICK_STATUS_SCRIPT = "get_brick_status.py";
private static final Logger logger = Logger.getLogger(Gluster323InterfaceService.class);
/* (non-Javadoc)
* @see org.gluster.storage.management.gateway.utils.GlusterInterface#addServer(java.lang.String, java.lang.String)
*/
@Override
public void addServer(String existingServer, String newServer) {
serverUtil.executeOnServer(existingServer, "gluster peer probe " + newServer);
// reverse peer probe to ensure that host names appear in peer status on both sides
serverUtil.executeOnServer(newServer, "gluster peer probe " + existingServer);
}
/* (non-Javadoc)
* @see org.gluster.storage.management.gateway.utils.GlusterInterface#startVolume(java.lang.String, java.lang.String)
*/
@Override
public void startVolume(String volumeName, String knownServer, Boolean force) {
serverUtil.executeOnServer(knownServer, "gluster volume start " + volumeName + ((force) ? " force" : ""));
}
/* (non-Javadoc)
* @see org.gluster.storage.management.gateway.utils.GlusterInterface#stopVolume(java.lang.String, java.lang.String)
*/
@Override
public void stopVolume(String volumeName, String knownServer, Boolean force) {
serverUtil.executeOnServer(knownServer, "gluster --mode=script volume stop " + volumeName
+ ((force) ? " force" : ""));
}
/* (non-Javadoc)
* @see org.gluster.storage.management.gateway.utils.GlusterInterface#resetOptions(java.lang.String, java.lang.String)
*/
@Override
public void resetOptions(String volumeName, String knownServer) {
serverUtil.executeOnServer(knownServer, "gluster volume reset " + volumeName);
}
/* (non-Javadoc)
* @see org.gluster.storage.management.gateway.utils.GlusterInterface#createVolume(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.Integer, java.lang.String, java.lang.String, java.lang.String)
*/
@Override
public void createVolume(String knownServer, String volumeName, String volumeTypeStr, String transportTypeStr,
Integer count, String bricks, String accessProtocols, String options) {
// TODO: Disable NFS if required depending on value of accessProtocols
VOLUME_TYPE volType = Volume.getVolumeTypeByStr(volumeTypeStr);
String volTypeArg = null;
if (volType == VOLUME_TYPE.REPLICATE || volType == VOLUME_TYPE.DISTRIBUTED_REPLICATE) {
volTypeArg = "replica";
} else if (volType == VOLUME_TYPE.STRIPE || volType == VOLUME_TYPE.DISTRIBUTED_STRIPE) {
volTypeArg = "stripe";
}
String transportTypeArg = null;
TRANSPORT_TYPE transportType = Volume.getTransportTypeByStr(transportTypeStr);
transportTypeArg = (transportType == TRANSPORT_TYPE.ETHERNET) ? "tcp" : "rdma";
String command = prepareVolumeCreateCommand(volumeName, StringUtil.extractList(bricks, ","), count,
volTypeArg, transportTypeArg);
serverUtil.executeOnServer(knownServer, command);
try {
createOptions(volumeName, StringUtil.extractMap(options, ",", "="), knownServer);
} catch(Exception e) {
throw new GlusterRuntimeException(
"Volume created successfully, however following errors occurred while setting options: "
+ CoreConstants.NEWLINE + e.getMessage());
}
}
private String prepareVolumeCreateCommand(String volumeName, List<String> brickDirectories, int count,
String volumeType, String transportTypeStr) {
StringBuilder command = new StringBuilder("gluster volume create " + volumeName + " ");
if (volumeType != null) {
command.append(volumeType + " " + count + " ");
}
command.append("transport " + transportTypeStr);
for (String brickDir : brickDirectories) {
command.append(" " + brickDir);
}
return command.toString();
}
/* (non-Javadoc)
* @see org.gluster.storage.management.gateway.utils.GlusterInterface#createOptions(java.lang.String, java.util.Map, java.lang.String)
*/
@Override
public void createOptions(String volumeName, Map<String, String> options, String knownServer) {
String errors = "";
if (options != null) {
for (Entry<String, String> option : options.entrySet()) {
String key = option.getKey();
String value = option.getValue();
try {
setOption(volumeName, key, value, knownServer);
} catch(Exception e) {
// append error
errors += e.getMessage() + CoreConstants.NEWLINE;
}
}
}
if (!errors.trim().isEmpty()) {
throw new GlusterRuntimeException("Errors while setting option(s) on volume [" + volumeName + "] : "
+ errors.trim());
}
}
/* (non-Javadoc)
* @see org.gluster.storage.management.gateway.utils.GlusterInterface#setOption(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
*/
@Override
public void setOption(String volumeName, String key, String value, String knownServer) {
serverUtil.executeOnServer(knownServer, "gluster volume set " + volumeName + " " + key + " " + "\""
+ value + "\"");
}
/* (non-Javadoc)
* @see org.gluster.storage.management.gateway.utils.GlusterInterface#deleteVolume(java.lang.String, java.lang.String)
*/
@Override
public void deleteVolume(String volumeName, String knownServer) {
serverUtil.executeOnServer(knownServer, "gluster --mode=script volume delete " + volumeName);
}
private String getVolumeInfo(String volumeName, String knownServer) {
return serverUtil.executeOnServer(knownServer, "gluster volume info " + volumeName, String.class);
}
private String getVolumeInfo(String knownServer) {
return serverUtil.executeOnServer(knownServer, "gluster volume info", String.class);
}
private boolean readVolumeType(Volume volume, String line) {
String volumeType = StringUtil.extractToken(line, VOLUME_TYPE_PFX);
if (volumeType != null) {
if (volumeType.equals(VOLUME_TYPE_DISTRIBUTE)) {
volume.setVolumeType(VOLUME_TYPE.DISTRIBUTE);
} else if (volumeType.equals(VOLUME_TYPE_REPLICATE)) {
volume.setVolumeType(VOLUME_TYPE.REPLICATE);
volume.setReplicaCount(Volume.DEFAULT_REPLICA_COUNT);
} else if ( volumeType.equals(VOLUME_TYPE_DISTRIBUTED_REPLICATTE) ){
volume.setVolumeType(VOLUME_TYPE.DISTRIBUTED_REPLICATE);
volume.setReplicaCount(Volume.DEFAULT_REPLICA_COUNT);
} else if ( volumeType.equals(VOLUME_TYPE_STRIPE) ){
volume.setVolumeType(VOLUME_TYPE.STRIPE);
volume.setReplicaCount(Volume.DEFAULT_REPLICA_COUNT);
} else if ( volumeType.equals(VOLUME_TYPE_DISTRIBUTED_STRIPE) ){
volume.setVolumeType(VOLUME_TYPE.DISTRIBUTED_STRIPE);
volume.setReplicaCount(Volume.DEFAULT_STRIPE_COUNT);
}
return true;
}
return false;
}
private void readReplicaOrStripeCount(Volume volume, String line) {
if (StringUtil.extractToken(line, "x") != null) {
// expected formated of line is "Number of Bricks: 3 x 2 = 6"
int count = Integer.parseInt(line.split("x")[1].split("=")[0].trim());
if (volume.getVolumeType() == VOLUME_TYPE.STRIPE
|| volume.getVolumeType() == VOLUME_TYPE.DISTRIBUTED_STRIPE) {
volume.setStripeCount(count);
} else if (volume.getVolumeType() == VOLUME_TYPE.REPLICATE
|| volume.getVolumeType() == VOLUME_TYPE.DISTRIBUTED_REPLICATE) {
volume.setReplicaCount(count);
volume.setStripeCount(0);
}
}
return;
}
private boolean readVolumeStatus(Volume volume, String line) {
String volumeStatus = StringUtil.extractToken(line, VOLUME_STATUS_PFX);
if (volumeStatus != null) {
volume.setStatus(volumeStatus.equals("Started") ? VOLUME_STATUS.ONLINE : VOLUME_STATUS.OFFLINE);
return true;
}
return false;
}
private boolean readTransportType(Volume volume, String line) {
String transportType = StringUtil.extractToken(line, VOLUME_TRANSPORT_TYPE_PFX);
if (transportType != null) {
volume.setTransportType(transportType.equals("tcp") ? TRANSPORT_TYPE.ETHERNET : TRANSPORT_TYPE.INFINIBAND);
return true;
}
return false;
}
private boolean readBrick(Volume volume, String brickLine) {
BRICK_STATUS brickStatus;
if (brickLine.matches("Brick[0-9]+:.*")) {
// line: "Brick1: server1:/export/md0/volume-name"
String brickName = brickLine.split(": ")[1];
String[] brickParts = brickLine.split(":");
String serverName = brickParts[1].trim();
String brickDir = brickParts[2].trim();
//To get the brick status
brickStatus = getBrickStatus(serverName, volume.getName(), brickName);
addBrickToVolume(volume, serverName, brickDir, brickStatus);
return true;
}
return false;
}
private void addBrickToVolume(Volume volume, String serverName, String brickDir, BRICK_STATUS status) {
volume.addBrick(new Brick(serverName, status, brickDir));
}
// Do not throw exception, Gracefully handle as Offline brick.
private BRICK_STATUS getBrickStatus(String serverName, String volumeName, String brick){
try {
String output = serverUtil.executeScriptOnServer(serverName, BRICK_STATUS_SCRIPT + " " + volumeName
+ " " + brick, String.class);
if (output.equals(CoreConstants.ONLINE)) {
return BRICK_STATUS.ONLINE;
} else {
return BRICK_STATUS.OFFLINE;
}
} catch(Exception e) { // Particularly interested on ConnectionExecption, if the server is offline
logger.warn("Exception while fetching brick status for [" + volumeName + "][" + brick
+ "]. Marking it as offline!", e);
return BRICK_STATUS.OFFLINE;
}
}
private boolean readBrickGroup(String line) {
return StringUtil.extractToken(line, VOLUME_BRICKS_GROUP_PFX) != null;
}
private boolean readOptionReconfigGroup(String line) {
return StringUtil.extractToken(line, VOLUME_OPTIONS_RECONFIG_PFX) != null;
}
private boolean readOption(Volume volume, String line) {
if (line.matches("^[^:]*:.*$")) {
int index = line.indexOf(':');
volume.setOption(line.substring(0, index).trim(), line.substring(index + 1, line.length()).trim());
if (line.substring(0, index).trim().equals(Volume.OPTION_NFS_DISABLE)) {
if (line.substring(index + 1, line.length()).trim().equals(GlusterConstants.ON)) {
volume.disableNFS();
} else {
volume.enableNFS();
}
}
return true;
}
return false;
}
/* (non-Javadoc)
* @see org.gluster.storage.management.gateway.utils.GlusterInterface#getVolume(java.lang.String, java.lang.String)
*/
@Override
public Volume getVolume(String volumeName, String knownServer) {
return parseVolumeInfo(getVolumeInfo(volumeName, knownServer)).get(0);
}
/* (non-Javadoc)
* @see org.gluster.storage.management.gateway.utils.GlusterInterface#getAllVolumes(java.lang.String)
*/
@Override
public List<Volume> getAllVolumes(String knownServer) {
return parseVolumeInfo(getVolumeInfo(knownServer));
}
private List<Volume> parseVolumeInfo(String volumeInfoText) {
List<Volume> volumes = new ArrayList<Volume>();
boolean isBricksGroupFound = false;
boolean isOptionReconfigFound = false;
Volume volume = null;
for (String line : volumeInfoText.split(CoreConstants.NEWLINE)) {
String volumeName = StringUtil.extractToken(line, VOLUME_NAME_PFX);
if (volumeName != null) {
if (volume != null) {
volumes.add(volume);
}
// prepare next volume to be read
volume = new Volume();
volume.setName(volumeName);
isBricksGroupFound = isOptionReconfigFound = false;
continue;
}
if (readVolumeType(volume, line))
continue;
if (StringUtil.extractToken(line, VOLUME_NUMBER_OF_BRICKS) != null) {
readReplicaOrStripeCount(volume, line);
}
if (readVolumeStatus(volume, line))
continue;
if (readTransportType(volume, line))
continue;
if (readBrickGroup(line)) {
isBricksGroupFound = true;
continue;
}
if (isBricksGroupFound) {
if (readBrick(volume, line)) {
continue;
} else {
isBricksGroupFound = false;
}
}
if (readOptionReconfigGroup(line)) {
isOptionReconfigFound = true;
continue;
}
if (isOptionReconfigFound) {
if (readOption(volume, line)) {
continue;
} else {
isOptionReconfigFound = false;
}
}
}
// add the last read volume
if (volume != null) {
volumes.add(volume);
}
return volumes;
}
/* (non-Javadoc)
* @see org.gluster.storage.management.gateway.utils.GlusterInterface#addBricks(java.lang.String, java.util.List, java.lang.String)
*/
@Override
public void addBricks(String volumeName, List<String> bricks, String knownServer) {
StringBuilder command = new StringBuilder("gluster volume add-brick " + volumeName);
for (String brickDir : bricks) {
command.append(" " + brickDir);
}
serverUtil.executeOnServer(knownServer, command.toString());
}
/* (non-Javadoc)
* @see org.gluster.storage.management.gateway.utils.GlusterInterface#getLogLocation(java.lang.String, java.lang.String, java.lang.String)
*/
@Override
public String getLogLocation(String volumeName, String brickName, String knownServer) {
String command = "gluster volume log locate " + volumeName + " " + brickName;
String output = serverUtil.executeOnServer(knownServer, command, String.class);
if (output.startsWith(VOLUME_LOG_LOCATION_PFX)) {
return output.substring(VOLUME_LOG_LOCATION_PFX.length()).trim();
}
throw new GlusterRuntimeException("Couldn't parse output of command [" + command + "]. Output [" + output
+ "] doesn't start with prefix [" + VOLUME_LOG_LOCATION_PFX + "]");
}
/* (non-Javadoc)
* @see org.gluster.storage.management.gateway.utils.GlusterInterface#getLogFileNameForBrickDir(java.lang.String)
*/
@Override
public String getLogFileNameForBrickDir(String serverName, String brickDir) {
String logFileName = brickDir;
if (logFileName.length() > 0 && logFileName.charAt(0) == '/') {
logFileName = logFileName.replaceFirst("/", "");
}
logFileName = logFileName.replaceAll("/", "-") + ".log";
return logFileName;
}
/* (non-Javadoc)
* @see org.gluster.storage.management.gateway.utils.GlusterInterface#removeBricks(java.lang.String, java.util.List, java.lang.String)
*/
@Override
public void removeBricks(String volumeName, List<String> bricks, String knownServer) {
StringBuilder command = new StringBuilder("gluster --mode=script volume remove-brick " + volumeName);
for (String brickDir : bricks) {
command.append(" " + brickDir);
}
serverUtil.executeOnServer(knownServer, command.toString());
}
/* (non-Javadoc)
* @see org.gluster.storage.management.gateway.utils.GlusterInterface#removeServer(java.lang.String, java.lang.String)
*/
@Override
public void removeServer(String existingServer, String serverName) {
serverUtil.executeOnServer(existingServer, "gluster --mode=script peer detach " + serverName);
}
/* (non-Javadoc)
* @see org.gluster.storage.management.gateway.utils.GlusterInterface#checkRebalanceStatus(java.lang.String, java.lang.String)
*/
@Override
public TaskStatus checkRebalanceStatus(String serverName, String volumeName) {
String command = "gluster volume rebalance " + volumeName + " status";
String output = serverUtil.executeOnServer(serverName, command, String.class).trim();
TaskStatus taskStatus = new TaskStatus();
if (output.matches("^rebalance completed.*")) {
taskStatus.setCode(Status.STATUS_CODE_SUCCESS);
} else if (output.matches(".*in progress.*")) {
taskStatus.setCode(Status.STATUS_CODE_RUNNING);
} else {
taskStatus.setCode(Status.STATUS_CODE_FAILURE);
}
taskStatus.setMessage(output);
return taskStatus;
}
/* (non-Javadoc)
* @see org.gluster.storage.management.gateway.utils.GlusterInterface#stopRebalance(java.lang.String, java.lang.String)
*/
@Override
public void stopRebalance(String serverName, String volumeName) {
String command = "gluster volume rebalance " + volumeName + " stop";
serverUtil.executeOnServer(serverName, command);
}
/**
* Performs given Brick Migration (replace-brick) Operation on given volume
*
* @param serverName
* The server on which the Gluster command will be executed. This must be part of the cluster to which
* the volume belongs.
* @param volumeName
* Volume on which the Brick Migration Operation is to be executed
* @param fromBrick
* The source Brick (being replaced)
* @param toBrick
* The destination Brick (which is replacing the source Brick)
* @param operation
* @return
*/
private String performBrickMigrationOperation(String serverName, String volumeName, String fromBrick,
String toBrick, String operation) {
String command = "gluster volume replace-brick " + volumeName + " " + fromBrick + " " + toBrick + " "
+ operation;
return serverUtil.executeOnServer(serverName, command, String.class);
}
/*
* (non-Javadoc)
*
* @see org.gluster.storage.management.gateway.utils.GlusterInterface#executeBrickMigration(java.lang.String,
* java.lang.String, java.lang.String, java.lang.String)
*/
@Override
public void startBrickMigration(String serverName, String volumeName, String fromBrick, String toBrick) {
performBrickMigrationOperation(serverName, volumeName, fromBrick, toBrick, "start");
}
/*
* (non-Javadoc)
* @see org.gluster.storage.management.gateway.services.GlusterInterface#pauseBrickMigration(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
*/
@Override
public void pauseBrickMigration(String serverName, String volumeName, String fromBrick, String toBrick) {
performBrickMigrationOperation(serverName, volumeName, fromBrick, toBrick, "pause");
}
/*
* (non-Javadoc)
* @see org.gluster.storage.management.gateway.services.GlusterInterface#stopBrickMigration(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
*/
@Override
public void stopBrickMigration(String serverName, String volumeName, String fromBrick, String toBrick) {
performBrickMigrationOperation(serverName, volumeName, fromBrick, toBrick, "abort");
}
/*
* (non-Javadoc)
* @see org.gluster.storage.management.gateway.services.GlusterInterface#commitBrickMigration(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
*/
@Override
public void commitBrickMigration(String serverName, String volumeName, String fromBrick, String toBrick) {
performBrickMigrationOperation(serverName, volumeName, fromBrick, toBrick, "commit");
}
/*
* (non-Javadoc)
* @see org.gluster.storage.management.gateway.services.GlusterInterface#checkBrickMigrationStatus(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
*/
@Override
public TaskStatus checkBrickMigrationStatus(String serverName, String volumeName, String fromBrick, String toBrick) {
String output = performBrickMigrationOperation(serverName, volumeName, fromBrick, toBrick, "status");
TaskStatus taskStatus = new TaskStatus();
if (output.matches("^Number of files migrated.*Migration complete$")
|| output.matches("^Number of files migrated = 0 .*Current file=")) {
// Note: Workaround - if no file in the volume brick to migrate,
// Gluster CLI is not giving proper (complete) status
taskStatus.setCode(Status.STATUS_CODE_COMMIT_PENDING);
taskStatus.setMessage(output.replaceAll("Migration complete", "Commit pending"));
} else if (output.matches("^Number of files migrated.*Current file=.*")) {
taskStatus.setCode(Status.STATUS_CODE_RUNNING);
} else if (output.matches("^replace brick has been paused.*")) {
taskStatus.setCode(Status.STATUS_CODE_PAUSE);
} else {
taskStatus.setCode(Status.STATUS_CODE_FAILURE);
}
taskStatus.setMessage(output);
return taskStatus;
}
/* (non-Javadoc)
* @see org.gluster.storage.management.gateway.utils.GlusterInterface#getVolumeOptionsInfo(java.lang.String)
*/
@Override
public VolumeOptionInfoListResponse getVolumeOptionsInfo(String serverName) {
return serverUtil.executeOnServer(serverName, "gluster volume set help-xml", VolumeOptionInfoListResponse.class);
}
public void logRotate(String volumeName, List<String> brickList, String knownServer) {
if (brickList == null || brickList.size() > 0) {
for (String brickDir : brickList) {
serverUtil.executeOnServer(knownServer, "gluster volume log rotate " + volumeName + " " + brickDir);
}
} else {
serverUtil.executeOnServer(knownServer, "gluster volume log rotate " + volumeName);
}
}
}