diff options
Diffstat (limited to 'src')
79 files changed, 4799 insertions, 667 deletions
diff --git a/src/com.gluster.storage.management.client/src/com/gluster/storage/management/client/AbstractClient.java b/src/com.gluster.storage.management.client/src/com/gluster/storage/management/client/AbstractClient.java index 807e32a3..a4df0e58 100644 --- a/src/com.gluster.storage.management.client/src/com/gluster/storage/management/client/AbstractClient.java +++ b/src/com.gluster.storage.management.client/src/com/gluster/storage/management/client/AbstractClient.java @@ -6,6 +6,7 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap;
import com.gluster.storage.management.client.utils.ClientUtil;
+import com.gluster.storage.management.core.constants.RESTConstants;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.DefaultClientConfig;
@@ -19,12 +20,12 @@ public abstract class AbstractClient { protected WebResource resource;
private String securityToken;
private String authHeader;
-
+
public AbstractClient() {
URI baseURI = new ClientUtil().getServerBaseURI();
resource = Client.create(new DefaultClientConfig()).resource(baseURI).path(getResourceName());
}
-
+
public AbstractClient(String securityToken) {
this();
setSecurityToken(securityToken);
@@ -92,9 +93,30 @@ public abstract class AbstractClient { }
/**
+ * Fetches the resource whose name is arrived at by appending the "subResourceName" parameter to the default
+ * resource (the one returned by {@link AbstractClient#getResourceName()})
+ *
+ * @param subResourceName
+ * Name of the sub-resource
+ * @param queryParams
+ * Query parameters to be sent for the GET request
+ * @param responseClass
+ * Expected class of the response
+ * @return Object of responseClass received as a result of the GET request on the sub-resource
+ */
+ @SuppressWarnings("rawtypes")
+ protected Object fetchSubResource(String subResourceName, MultivaluedMap<String, String> queryParams,
+ Class responseClass) {
+ return fetchResource(resource.path(subResourceName), queryParams, responseClass);
+ }
+
+ /**
* Submits given Form using POST method to the resource and returns the object received as response
- * @param responseClass Class of the object expected as response
- * @param form Form to be submitted
+ *
+ * @param responseClass
+ * Class of the object expected as response
+ * @param form
+ * Form to be submitted
* @return Object of given class received as response
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
@@ -105,9 +127,13 @@ public abstract class AbstractClient { /**
* Submits given Form using POST method to the given sub-resource and returns the object received as response
- * @param subResourceName Name of the sub-resource to which the request is to be posted
- * @param responseClass Class of the object expected as response
- * @param form Form to be submitted
+ *
+ * @param subResourceName
+ * Name of the sub-resource to which the request is to be posted
+ * @param responseClass
+ * Class of the object expected as response
+ * @param form
+ * Form to be submitted
* @return Object of given class received as response
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@@ -118,9 +144,13 @@ public abstract class AbstractClient { /**
* Submits given Form using PUT method to the given sub-resource and returns the object received as response
- * @param subResourceName Name of the sub-resource to which the request is to be posted
- * @param responseClass Class of the object expected as response
- * @param form Form to be submitted
+ *
+ * @param subResourceName
+ * Name of the sub-resource to which the request is to be posted
+ * @param responseClass
+ * Class of the object expected as response
+ * @param form
+ * Form to be submitted
* @return Object of given class received as response
*/
protected Object putRequest(String subResourceName, Class responseClass, Form form) {
@@ -130,8 +160,11 @@ public abstract class AbstractClient { /**
* Submits given Form using PUT method to the given sub-resource and returns the object received as response
- * @param subResourceName Name of the sub-resource to which the request is to be posted
- * @param responseClass Class of the object expected as response
+ *
+ * @param subResourceName
+ * Name of the sub-resource to which the request is to be posted
+ * @param responseClass
+ * Class of the object expected as response
* @return Object of given class received as response
*/
protected Object putRequest(String subResourceName, Class responseClass) {
@@ -141,8 +174,11 @@ public abstract class AbstractClient { /**
* Submits given object to the resource and returns the object received as response
- * @param responseClass Class of the object expected as response
- * @param requestObject the Object to be submitted
+ *
+ * @param responseClass
+ * Class of the object expected as response
+ * @param requestObject
+ * the Object to be submitted
* @return Object of given class received as response
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
@@ -151,6 +187,19 @@ public abstract class AbstractClient { .post(responseClass, requestObject);
}
+ @SuppressWarnings("unchecked")
+ protected Object deleteResource(Class responseClass, Form form) {
+ return resource.header(HTTP_HEADER_AUTH, authHeader).delete(responseClass);
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ protected Object deleteSubResource(String subResourceName, Class responseClass, String volumeName,
+ String deleteOption) {
+ return resource.path(subResourceName).queryParam(RESTConstants.QUERY_PARAM_VOLUME_NAME, volumeName)
+ .queryParam(RESTConstants.QUERY_PARAM_DELETE_OPTION, deleteOption).header(HTTP_HEADER_AUTH, authHeader)
+ .delete(responseClass);
+ }
+
public abstract String getResourceName();
/**
@@ -161,7 +210,8 @@ public abstract class AbstractClient { }
/**
- * @param securityToken the securityToken to set
+ * @param securityToken
+ * the securityToken to set
*/
protected void setSecurityToken(String securityToken) {
this.securityToken = securityToken;
diff --git a/src/com.gluster.storage.management.client/src/com/gluster/storage/management/client/DiscoveredServersClient.java b/src/com.gluster.storage.management.client/src/com/gluster/storage/management/client/DiscoveredServersClient.java index 6a22bf5f..c7ea7507 100644 --- a/src/com.gluster.storage.management.client/src/com/gluster/storage/management/client/DiscoveredServersClient.java +++ b/src/com.gluster.storage.management.client/src/com/gluster/storage/management/client/DiscoveredServersClient.java @@ -41,17 +41,21 @@ public class DiscoveredServersClient extends AbstractClient { return RESOURCE_NAME; } + @SuppressWarnings("rawtypes") private Object getDiscoveredServers(Boolean getDetails, Class responseClass) { MultivaluedMap<String, String> queryParams = new MultivaluedMapImpl(); queryParams.putSingle("details", getDetails.toString()); + //System.out.println((String) fetchResource(queryParams, String.class)); return ((Response) fetchResource(queryParams, responseClass)).getData(); } + @SuppressWarnings("unchecked") public List<String> getDiscoveredServerNames() { return (List<String>) getDiscoveredServers(Boolean.FALSE, StringListResponse.class); } + @SuppressWarnings("unchecked") public List<Server> getDiscoveredServerDetails() { return (List<Server>) getDiscoveredServers(Boolean.TRUE, ServerListResponse.class); } diff --git a/src/com.gluster.storage.management.client/src/com/gluster/storage/management/client/GlusterDataModelManager.java b/src/com.gluster.storage.management.client/src/com/gluster/storage/management/client/GlusterDataModelManager.java index 0f932df0..41d76e58 100644 --- a/src/com.gluster.storage.management.client/src/com/gluster/storage/management/client/GlusterDataModelManager.java +++ b/src/com.gluster.storage.management.client/src/com/gluster/storage/management/client/GlusterDataModelManager.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; +import java.util.Map.Entry; import com.gluster.storage.management.core.exceptions.GlusterRuntimeException; import com.gluster.storage.management.core.model.Cluster; @@ -41,9 +42,10 @@ import com.gluster.storage.management.core.model.Volume; import com.gluster.storage.management.core.model.Volume.TRANSPORT_TYPE; import com.gluster.storage.management.core.model.Volume.VOLUME_STATUS; import com.gluster.storage.management.core.model.Volume.VOLUME_TYPE; +import com.gluster.storage.management.core.model.VolumeOptionInfo; import com.gluster.storage.management.core.response.RunningTaskListResponse; import com.gluster.storage.management.core.response.VolumeListResponse; -import com.gluster.storage.management.client.VolumesClient; +import com.gluster.storage.management.core.response.VolumeOptionInfoListResponse; public class GlusterDataModelManager { // private Server discoveredServer1, discoveredServer2, discoveredServer3, @@ -57,6 +59,7 @@ public class GlusterDataModelManager { private String securityToken; private String serverName; private List<ClusterListener> listeners = new ArrayList<ClusterListener>(); + private List<VolumeOptionInfo> volumeOptionsDefaults; private GlusterDataModelManager() { } @@ -114,13 +117,9 @@ public class GlusterDataModelManager { setSecurityToken(securityToken); Cluster cluster = new Cluster("Home", model); - VolumesClient volumeClient = new VolumesClient(securityToken); initializeGlusterServers(cluster); - - // initializeVolumes(cluster); - VolumeListResponse response = volumeClient.getAllVolumes(); - cluster.setVolumes(response.getVolumes()); + initializeVolumes(cluster); initializeAutoDiscoveredServers(cluster); initializeDisks(); @@ -130,12 +129,21 @@ public class GlusterDataModelManager { createDummyLogMessages(); initializeRunningTasks(cluster); - initializeAlerts(cluster); + initializeVolumeOptionsDefaults(); model.addCluster(cluster); } + private void initializeVolumeOptionsDefaults() { + VolumeOptionInfoListResponse response = new VolumesClient(getSecurityToken()).getVolumeOptionsDefaults(); + if (!response.getStatus().isSuccess()) { + throw new GlusterRuntimeException("Error fetching volume option defaults: [" + + response.getStatus().getMessage() + "]"); + } + this.volumeOptionsDefaults = response.getOptions(); + } + private void addVolumeOptions() { for (Volume vol : new Volume[] { volume1, volume2, volume3, volume4, volume5 }) { for (int i = 1; i <= 5; i++) { @@ -155,26 +163,12 @@ public class GlusterDataModelManager { } private void initializeVolumes(Cluster cluster) { - List<Volume> volumes = new ArrayList<Volume>(); - - volume1 = addVolume(volumes, "Volume1", cluster, VOLUME_TYPE.PLAIN_DISTRIBUTE, TRANSPORT_TYPE.ETHERNET, - VOLUME_STATUS.ONLINE); - - volume2 = addVolume(volumes, "Volume2", cluster, VOLUME_TYPE.PLAIN_DISTRIBUTE, TRANSPORT_TYPE.ETHERNET, - VOLUME_STATUS.ONLINE); - - volume3 = addVolume(volumes, "Volume3", cluster, VOLUME_TYPE.DISTRIBUTED_MIRROR, TRANSPORT_TYPE.ETHERNET, - VOLUME_STATUS.OFFLINE); - volume3.setReplicaCount(2); - - volume4 = addVolume(volumes, "Volume4", cluster, VOLUME_TYPE.PLAIN_DISTRIBUTE, TRANSPORT_TYPE.ETHERNET, - VOLUME_STATUS.ONLINE); - - volume5 = addVolume(volumes, "Volume5", cluster, VOLUME_TYPE.DISTRIBUTED_STRIPE, TRANSPORT_TYPE.INFINIBAND, - VOLUME_STATUS.OFFLINE); - volume5.setStripeCount(3); - - cluster.setVolumes(volumes); + VolumesClient volumeClient = new VolumesClient(securityToken); + VolumeListResponse response = volumeClient.getAllVolumes(); + if (!response.getStatus().isSuccess()) { + throw new GlusterRuntimeException("Error fetching volume list: [" + response.getStatus() + "]"); + } + cluster.setVolumes(response.getVolumes()); } private void initializeDisks() { @@ -186,13 +180,8 @@ public class GlusterDataModelManager { s2dc = new Disk(server2, "sdc", 200d, -1d, DISK_STATUS.UNINITIALIZED); s2dd = new Disk(server2, "sdd", 200d, 124.89, DISK_STATUS.READY); - s3da = new Disk(server3, "NA", -1d, -1d, DISK_STATUS.OFFLINE); // disk - // name - // unavailable - // since - // server - // is - // offline + // disk name unavailable since server is offline + s3da = new Disk(server3, "NA", -1d, -1d, DISK_STATUS.OFFLINE); s4da = new Disk(server4, "sda", 100d, 85.39, DISK_STATUS.READY); @@ -250,7 +239,7 @@ public class GlusterDataModelManager { private void addMessages(List<LogMessage> messages, Disk disk, String severity, int count) { for (int i = 1; i <= count; i++) { String message = severity + "message" + i; - messages.add(new LogMessage(new Date(), disk, severity, message)); + messages.add(new LogMessage(new Date(), disk.getQualifiedName(), severity, message)); } } @@ -294,7 +283,7 @@ public class GlusterDataModelManager { List<Disk> allDisks = getReadyDisksOfAllServers(); String brickInfo[] = volumeDisk.split(":"); for (Disk disk : allDisks) { - if (disk.getServerName() == brickInfo[0] && disk.getName() == brickInfo[1]) { + if (disk.getServerName().equals(brickInfo[0]) && disk.getName().equals(brickInfo[1])) { return disk; } } @@ -305,8 +294,8 @@ public class GlusterDataModelManager { /* * TODO: review the logic * - * List<Disk> disks = new ArrayList<Disk>(); for (Disk disk : - * volume.getDisks()) { if (disk.isReady()) { disks.add(disk); } } + * List<Disk> disks = new ArrayList<Disk>(); for (Disk disk : volume.getDisks()) { if (disk.isReady()) { + * disks.add(disk); } } */ Disk disk = null; List<Disk> volumeDisks = new ArrayList<Disk>(); @@ -370,6 +359,15 @@ public class GlusterDataModelManager { } } + public void deleteVolume(Volume volume) { + Cluster cluster = model.getCluster(); + cluster.deleteVolume(volume); + + for (ClusterListener listener : listeners) { + listener.volumeDeleted(volume); + } + } + public void updateVolumeStatus(Volume volume, VOLUME_STATUS newStatus) { volume.setStatus(newStatus); for (ClusterListener listener : listeners) { @@ -377,6 +375,20 @@ public class GlusterDataModelManager { } } + public void resetVolumeOptions(Volume volume) { + volume.getOptions().clear(); + for (ClusterListener listener : listeners) { + listener.volumeChanged(volume, new Event(EVENT_TYPE.VOLUME_OPTIONS_RESET, null)); + } + } + + public void setVolumeOption(Volume volume, Entry<String, String> entry) { + volume.setOption(entry.getKey(), (String) entry.getValue()); + for (ClusterListener listener : listeners) { + listener.volumeChanged(volume, new Event(EVENT_TYPE.VOLUME_OPTION_SET, entry)); + } + } + public void addVolume(Volume volume) { Cluster cluster = model.getCluster(); cluster.addVolume(volume); @@ -385,4 +397,41 @@ public class GlusterDataModelManager { listener.volumeCreated(volume); } } + + public List<VolumeOptionInfo> getVolumeOptionsDefaults() { + return volumeOptionsDefaults; + } + + public VolumeOptionInfo getVolumeOptionInfo(String optionKey) { + for (VolumeOptionInfo info : volumeOptionsDefaults) { + if (info.getName().equals(optionKey)) { + return info; + } + } + throw new GlusterRuntimeException("Invalid option key [" + optionKey + + "] passed to GlusterDataModelManager#getVolumeOptionInfo"); + } + + public String getVolumeOptionDefaultValue(String optionKey) { + return getVolumeOptionInfo(optionKey).getDefaultValue(); + } + + public String getVolumeOptionDesc(String optionKey) { + return getVolumeOptionInfo(optionKey).getDescription(); + } + + public void setAccessControlList(Volume volume, String accessControlList) { + volume.setAccessControlList(accessControlList); + setVolumeOption(volume, getOptionEntry(volume, Volume.OPTION_AUTH_ALLOW)); + } + + private Entry<String, String> getOptionEntry(Volume volume, String optionKey) { + for (Entry entry : volume.getOptions().entrySet()) { + if (entry.getKey().equals(optionKey)) { + return entry; + } + } + throw new GlusterRuntimeException("Couldn't find entry for option [" + optionKey + "] on volume [" + + volume.getName()); + } } diff --git a/src/com.gluster.storage.management.client/src/com/gluster/storage/management/client/VolumesClient.java b/src/com.gluster.storage.management.client/src/com/gluster/storage/management/client/VolumesClient.java index a479e1f7..33791a50 100644 --- a/src/com.gluster.storage.management.client/src/com/gluster/storage/management/client/VolumesClient.java +++ b/src/com.gluster.storage.management.client/src/com/gluster/storage/management/client/VolumesClient.java @@ -20,18 +20,28 @@ */ package com.gluster.storage.management.client; +import java.util.ArrayList; +import java.util.Date; import java.util.List; +import javax.ws.rs.core.MultivaluedMap; +import com.gluster.storage.management.core.constants.CoreConstants; +import com.gluster.storage.management.core.constants.GlusterConstants; import com.gluster.storage.management.core.constants.RESTConstants; +import com.gluster.storage.management.core.model.Disk; +import com.gluster.storage.management.core.model.Disk.DISK_STATUS; import com.gluster.storage.management.core.model.Status; import com.gluster.storage.management.core.model.Volume; -import com.gluster.storage.management.core.model.VolumeOptionInfo; +import com.gluster.storage.management.core.response.LogMessageListResponse; import com.gluster.storage.management.core.response.VolumeListResponse; import com.gluster.storage.management.core.response.VolumeOptionInfoListResponse; +import com.gluster.storage.management.core.utils.DateUtil; +import com.gluster.storage.management.core.utils.GlusterCoreUtil; +import com.gluster.storage.management.core.utils.StringUtil; import com.sun.jersey.api.representation.Form; +import com.sun.jersey.core.util.MultivaluedMapImpl; public class VolumesClient extends AbstractClient { - public VolumesClient(String securityToken) { super(securityToken); } @@ -44,14 +54,14 @@ public class VolumesClient extends AbstractClient { public Status createVolume(Volume volume) { return (Status) postObject(Status.class, volume); } - + private Status performOperation(String volumeName, String operation) { Form form = new Form(); form.add(RESTConstants.FORM_PARAM_OPERATION, operation); - - return (Status)putRequest(volumeName, Status.class, form); + + return (Status) putRequest(volumeName, Status.class, form); } - + public Status startVolume(String volumeName) { return performOperation(volumeName, RESTConstants.FORM_PARAM_VALUE_START); } @@ -59,34 +69,97 @@ public class VolumesClient extends AbstractClient { public Status stopVolume(String volumeName) { return performOperation(volumeName, RESTConstants.FORM_PARAM_VALUE_STOP); } - + public Status setVolumeOption(String volume, String key, String value) { Form form = new Form(); form.add(RESTConstants.FORM_PARAM_OPTION_KEY, key); form.add(RESTConstants.FORM_PARAM_OPTION_VALUE, value); - return (Status)postRequest(volume + "/" + RESTConstants.SUBRESOURCE_OPTIONS, Status.class, form); + return (Status) postRequest(volume + "/" + RESTConstants.SUBRESOURCE_OPTIONS, Status.class, form); } - + public Status resetVolumeOptions(String volume) { - return (Status)putRequest(volume, Status.class); + return (Status) putRequest(volume + "/" + RESTConstants.SUBRESOURCE_OPTIONS, Status.class); } - + public VolumeListResponse getAllVolumes() { return (VolumeListResponse) fetchResource(VolumeListResponse.class); } - + public Volume getVolume(String volumeName) { return (Volume) fetchSubResource(volumeName, Volume.class); } - public List<VolumeOptionInfo> getVolumeOptionsDefaults() { - String responseStr = (String)fetchSubResource( - RESTConstants.SUBRESOURCE_DEFAULT_OPTIONS, String.class); - System.out.println(responseStr); + public Status deleteVolume(Volume volume, String deleteOption) { + return (Status) deleteSubResource(volume.getName(), Status.class, volume.getName(), deleteOption); + } + + public VolumeOptionInfoListResponse getVolumeOptionsDefaults() { + return ((VolumeOptionInfoListResponse) fetchSubResource(RESTConstants.SUBRESOURCE_DEFAULT_OPTIONS, + VolumeOptionInfoListResponse.class)); + } + + public Status addDisks(String volumeName, List<Disk> diskList) { + String disks = StringUtil.ListToString( GlusterCoreUtil.getQualifiedDiskNames(diskList), ","); + Form form = new Form(); + form.add(RESTConstants.QUERY_PARAM_DISKS, disks); + return (Status) postRequest(volumeName + "/" + RESTConstants.SUBRESOURCE_DISKS, Status.class, form); + } + + public Status addDisks(String volumeName, String disks) { + Form form = new Form(); + form.add(RESTConstants.QUERY_PARAM_DISKS, disks); + return (Status) postRequest(volumeName + "/" + RESTConstants.SUBRESOURCE_DISKS, Status.class, form); + } + + /** + * Fetches volume logs for the given volume based on given filter criteria + * + * @param volumeName + * Name of volume whose logs are to be fetched + * @param diskName + * Name of the disk whose logs are to be fetched. Pass ALL to fetch log messages from all disks of the + * volume. + * @param severity + * Log severity {@link GlusterConstants#VOLUME_LOG_LEVELS_ARR}. Pass ALL to fetch log messages of all + * severity levels. + * @param fromTimestamp + * From timestamp. Pass null if this filter is not required. + * @param toTimestamp + * To timestamp. Pass null if this filter is not required. + * @param messageCount + * Number of most recent log messages to be fetched (from each disk) + * @return Log Message List response received from the Gluster Management Server. + */ + public LogMessageListResponse getLogs(String volumeName, String diskName, String severity, Date fromTimestamp, Date toTimestamp, int messageCount) { + MultivaluedMap<String, String> queryParams = prepareGetLogQueryParams(diskName, severity, fromTimestamp, + toTimestamp, messageCount); + + return (LogMessageListResponse) fetchSubResource(volumeName + "/" + RESTConstants.SUBRESOURCE_LOGS, + queryParams, LogMessageListResponse.class); + } + + private MultivaluedMap<String, String> prepareGetLogQueryParams(String diskName, String severity, + Date fromTimestamp, Date toTimestamp, int messageCount) { + MultivaluedMap<String, String> queryParams = new MultivaluedMapImpl(); + queryParams.add(RESTConstants.QUERY_PARAM_LINE_COUNT, "" + messageCount); + if(!diskName.equals(CoreConstants.ALL)) { + queryParams.add(RESTConstants.QUERY_PARAM_DISK_NAME, diskName); + } + + if (!severity.equals(CoreConstants.ALL)) { + queryParams.add(RESTConstants.QUERY_PARAM_LOG_SEVERITY, severity); + } - VolumeOptionInfoListResponse response = (VolumeOptionInfoListResponse) fetchSubResource( - RESTConstants.SUBRESOURCE_DEFAULT_OPTIONS, VolumeOptionInfoListResponse.class); - return response.getOptions(); + if (fromTimestamp != null) { + queryParams.add(RESTConstants.QUERY_PARAM_FROM_TIMESTAMP, + DateUtil.dateToString(fromTimestamp, CoreConstants.DATE_WITH_TIME_FORMAT)); + } + + if (toTimestamp != null) { + queryParams.add(RESTConstants.QUERY_PARAM_TO_TIMESTAMP, + DateUtil.dateToString(toTimestamp, CoreConstants.DATE_WITH_TIME_FORMAT)); + } + return queryParams; } public static void main(String[] args) { @@ -109,8 +182,17 @@ public class VolumesClient extends AbstractClient { // for (VolumeOptionInfo option : client.getVolumeOptionsDefaults()) { // System.out.println(option.getName() + "-" + option.getDescription() + "-" + option.getDefaultValue()); // } - System.out.println(client.getVolume("Volume3").getOptions()); - System.out.println(client.setVolumeOption("Volume3", "network.frame-timeout", "600").getMessage()); +// System.out.println(client.getVolume("Volume3").getOptions()); +// System.out.println(client.setVolumeOption("Volume3", "network.frame-timeout", "600").getMessage()); + List<Disk> disks = new ArrayList<Disk>(); + Disk disk = new Disk(); + disk.setServerName("server1"); + disk.setName("sda"); + disk.setStatus(DISK_STATUS.READY); + disks.add(disk); + + Status status = client.addDisks("Volume3", disks); + System.out.println(status.getMessage()); } } } diff --git a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/constants/CoreConstants.java b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/constants/CoreConstants.java index 177334bd..8a1e1f37 100644 --- a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/constants/CoreConstants.java +++ b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/constants/CoreConstants.java @@ -26,4 +26,8 @@ public class CoreConstants { public static final String NEWLINE = System.getProperty("line.separator"); public static final String FILE_SEPARATOR = System.getProperty("file.separator"); public static final String ENCODING_UTF8 = "UTF-8"; + public static final String ALL = "ALL"; + public static final String DATE_WITH_TIME_FORMAT = "MM/dd/yyyy HH:mm:ss"; + public static final String PURE_DATE_FORMAT = "MM/dd/yyyy"; + public static final String PURE_TIME_FORMAT = "HH:mm:ss.SSS"; } diff --git a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/constants/GlusterConstants.java b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/constants/GlusterConstants.java new file mode 100644 index 00000000..064037ec --- /dev/null +++ b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/constants/GlusterConstants.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero 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 Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + *******************************************************************************/ +package com.gluster.storage.management.core.constants; + +import java.util.List; + +import com.gluster.storage.management.core.utils.StringUtil; + +/** + * + */ +public class GlusterConstants { + public enum VOLUME_LOG_LEVELS { + EMERGENCY, ALERT, CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG, TRACE + }; + + public static final List<String> VOLUME_LOG_LEVELS_ARR = StringUtil.enumToArray(VOLUME_LOG_LEVELS.values()); + +} diff --git a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/constants/RESTConstants.java b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/constants/RESTConstants.java index b5b51cfd..159c408e 100644 --- a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/constants/RESTConstants.java +++ b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/constants/RESTConstants.java @@ -26,15 +26,30 @@ package com.gluster.storage.management.core.constants; public class RESTConstants { // Volumes Resource public static final String RESOURCE_PATH_VOLUMES = "/cluster/volumes"; + public static final String SUBRESOURCE_DEFAULT_OPTIONS = "defaultoptions"; + public static final String SUBRESOURCE_OPTIONS = "options"; + public static final String SUBRESOURCE_LOGS = "logs"; + public static final String SUBRESOURCE_DISKS = "disks"; + + public static final String FORM_PARAM_OPERATION = "operation"; public static final String FORM_PARAM_VALUE_START = "start"; public static final String FORM_PARAM_VALUE_STOP = "stop"; public static final String FORM_PARAM_OPTION_KEY = "key"; public static final String FORM_PARAM_OPTION_VALUE = "value"; + public static final String PATH_PARAM_VOLUME_NAME = "volumeName"; - public static final String SUBRESOURCE_DEFAULT_OPTIONS = "defaultoptions"; - public static final String SUBRESOURCE_OPTIONS = "options"; + public static final String FORM_PARAM_DELETE_OPTION = "value"; + public static final String QUERY_PARAM_DISK_NAME = "diskName"; + public static final String QUERY_PARAM_DISKS = "disks"; + public static final String QUERY_PARAM_LINE_COUNT = "lineCount"; + public static final String QUERY_PARAM_VOLUME_NAME = "volumeName"; + public static final String QUERY_PARAM_DELETE_OPTION = "deleteOption"; + public static final String QUERY_PARAM_LOG_SEVERITY = "severity"; + public static final String QUERY_PARAM_FROM_TIMESTAMP = "fromTimestamp"; + public static final String QUERY_PARAM_TO_TIMESTAMP = "toTimestamp"; + // Running tasks resource public static final String RESOURCE_PATH_RUNNING_TASKS = "/cluster/runningtasks"; public static final String RESOURCE_PATH_ALERTS = "/cluster/alerts"; diff --git a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/Alert.java b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/Alert.java index 66107734..c0077a30 100644 --- a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/Alert.java +++ b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/Alert.java @@ -14,7 +14,7 @@ public class Alert { protected String id; protected ALERT_TYPES type; - protected String reference; + protected String reference; // [for server- "Server", for Disk- "Server:disk", for volume- "Volume:Server:disk"] protected String message; public String getAlertType(ALERT_TYPES alertType) { diff --git a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/Cluster.java b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/Cluster.java index 1af57266..8fa247a1 100644 --- a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/Cluster.java +++ b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/Cluster.java @@ -54,6 +54,10 @@ public class Cluster extends Entity { discoveredServers.remove(server); } + public void deleteVolume(Volume volume) { + volumes.remove(volume); + } + public void setServers(List<GlusterServer> servers) { this.servers = servers; children.add(new EntityGroup<GlusterServer>("Servers", GlusterServer.class, this, servers)); @@ -67,6 +71,15 @@ public class Cluster extends Entity { this.discoveredServers = autoDiscoveredServers; children.add(new EntityGroup<Server>("Discovered Servers", Server.class, this, autoDiscoveredServers)); } + + public EntityGroup<Server> getAutoDiscoveredServersEntityGroup() { + for(Entity entity : getChildren()) { + if(entity instanceof EntityGroup && ((EntityGroup)entity).getEntityType() == Server.class) { + return (EntityGroup<Server>)entity; + } + } + return null; + } public List<Volume> getVolumes() { return volumes; diff --git a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/ClusterListener.java b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/ClusterListener.java index ce2752a4..f96116ed 100644 --- a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/ClusterListener.java +++ b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/ClusterListener.java @@ -40,4 +40,6 @@ public interface ClusterListener { public void volumeChanged(Volume volume, Event event); public void volumeCreated(Volume volume); + + public void volumeDeleted(Volume volume); } diff --git a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/DefaultClusterListener.java b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/DefaultClusterListener.java index 1a39a014..e226d51b 100644 --- a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/DefaultClusterListener.java +++ b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/DefaultClusterListener.java @@ -69,6 +69,11 @@ public class DefaultClusterListener implements ClusterListener { clusterChanged(); } + @Override + public void volumeDeleted(Volume volume) { + clusterChanged(); + } + /** * This method is called by every other event method. Thus, if a view/listener is interested in performing the same * task on any change happening in the cluster data model, it can simply override this method and implement the diff --git a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/Event.java b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/Event.java index 65501a2b..22b938d2 100644 --- a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/Event.java +++ b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/Event.java @@ -24,7 +24,9 @@ public class Event { DISK_REMOVED, NETWORK_INTERFACE_ADDED, NETWORK_INTERFACE_REMOVED, - VOLUME_STATUS_CHANGED + VOLUME_STATUS_CHANGED, + VOLUME_OPTIONS_RESET, + VOLUME_OPTION_SET } private EVENT_TYPE eventType; diff --git a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/GlusterDummyModel.java b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/GlusterDummyModel.java index f25999f0..09137014 100644 --- a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/GlusterDummyModel.java +++ b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/GlusterDummyModel.java @@ -218,7 +218,7 @@ public class GlusterDummyModel { private void addMessages(List<LogMessage> messages, Disk disk, String severity, int count) { for (int i = 1; i <= count; i++) { String message = severity + "message" + i; - messages.add(new LogMessage(new Date(), disk, severity, message)); + messages.add(new LogMessage(new Date(), disk.getQualifiedName(), severity, message)); } } diff --git a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/LogMessage.java b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/LogMessage.java index cc3aa043..4f6347dc 100644 --- a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/LogMessage.java +++ b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/LogMessage.java @@ -20,14 +20,29 @@ package com.gluster.storage.management.core.model; import java.util.Date; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +import com.gluster.storage.management.core.model.adapters.VolumeLogDateAdapter; import com.gluster.storage.management.core.utils.StringUtil; +@XmlRootElement public class LogMessage implements Filterable { private Date timestamp; - private Disk disk; + private String disk; private String severity; private String message; + public LogMessage() { + } + + public LogMessage(String logMessage) { + // TODO: Parse the log message and extract fields + } + + @XmlElement(name = "timestamp", required = true) + @XmlJavaTypeAdapter(VolumeLogDateAdapter.class) public Date getTimestamp() { return timestamp; } @@ -36,11 +51,11 @@ public class LogMessage implements Filterable { this.timestamp = timestamp; } - public Disk getDisk() { + public String getDisk() { return disk; } - public void setDisk(Disk disk) { + public void setDisk(String disk) { this.disk = disk; } @@ -60,7 +75,7 @@ public class LogMessage implements Filterable { this.message = message; } - public LogMessage(Date timestamp, Disk disk, String severity, String message) { + public LogMessage(Date timestamp, String disk, String severity, String message) { setTimestamp(timestamp); setDisk(disk); setSeverity(severity); @@ -69,7 +84,7 @@ public class LogMessage implements Filterable { @Override public boolean filter(String filterString, boolean caseSensitive) { - return StringUtil.filterString(getSeverity() + getTimestamp() + getDisk().getServerName() - + getDisk().getQualifiedName() + getMessage(), filterString, caseSensitive); + return StringUtil.filterString(getSeverity() + getTimestamp() + getDisk() + getMessage(), filterString, + caseSensitive); } } diff --git a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/Status.java b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/Status.java index c5fdb246..95075f78 100644 --- a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/Status.java +++ b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/Status.java @@ -24,14 +24,16 @@ import com.gluster.storage.management.core.utils.ProcessResult; @XmlRootElement(name = "status") public class Status { + // TODO: Convert the status codes to an enumeration public static final int STATUS_CODE_SUCCESS = 0; public static final int STATUS_CODE_FAILURE = 1; - public static final int STATUS_CODE_RUNNING = 2; - public static final int STATUS_CODE_PAUSE = 3; - public static final int STATUS_CODE_WARNING = 4; + public static final int STATUS_CODE_PART_SUCCESS = 2; + public static final int STATUS_CODE_RUNNING = 3; + public static final int STATUS_CODE_PAUSE = 4; + public static final int STATUS_CODE_WARNING = 5; public static final Status STATUS_SUCCESS = new Status(STATUS_CODE_SUCCESS, "Success"); public static final Status STATUS_FAILURE = new Status(STATUS_CODE_FAILURE, "Failure"); - + // public static final Status private Integer code; @@ -43,6 +45,10 @@ public class Status { public boolean isSuccess() { return code == STATUS_CODE_SUCCESS; } + + public boolean isPartSuccess() { + return code == STATUS_CODE_PART_SUCCESS; + } public Status(Integer code, String message) { this.code = code; @@ -53,6 +59,11 @@ public class Status { this.code = result.getExitValue(); this.message = result.getOutput(); } + + public Status(Exception e) { + this.code = STATUS_CODE_FAILURE; + this.message = e.getMessage(); + } @XmlElement(name = "code", type = Integer.class) public Integer getCode() { @@ -74,6 +85,6 @@ public class Status { @Override public String toString() { - return (isSuccess() ? "Success" : "Failure [" + getCode() + "]") + ": " + getMessage(); + return isSuccess() ? "Success" : "[" + getCode() + "][" + getMessage() + "]"; } }
\ No newline at end of file diff --git a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/Volume.java b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/Volume.java index baa3edb9..41ed5a25 100644 --- a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/Volume.java +++ b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/Volume.java @@ -49,7 +49,11 @@ public class Volume extends Entity { GLUSTERFS, NFS }; - private static final String OPTION_AUTH_ALLOW = "auth.allow:"; + + public static final int DEFAULT_REPLICA_COUNT = 2; + public static final int DEFAULT_STRIPE_COUNT = 4; + + public static final String OPTION_AUTH_ALLOW = "auth.allow"; private static final String[] VOLUME_TYPE_STR = new String[] { "Plain Distribute", "Distributed Mirror", "Distributed Stripe" }; @@ -67,15 +71,14 @@ public class Volume extends Entity { private double totalDiskSpace = 0; private List<String> disks = new ArrayList<String>(); + private List<String> bricks = new ArrayList<String>(); public Volume() { } - // GlusterFS export is always enabled - private Set<NAS_PROTOCOL> nasProtocols = new LinkedHashSet<NAS_PROTOCOL>( - Arrays.asList(new NAS_PROTOCOL[] { NAS_PROTOCOL.GLUSTERFS })); - - private String accessControlList = "*"; + // GlusterFS and NFS export is always enabled + private Set<NAS_PROTOCOL> nasProtocols = new LinkedHashSet<NAS_PROTOCOL>(Arrays.asList(new NAS_PROTOCOL[] { + NAS_PROTOCOL.GLUSTERFS, NAS_PROTOCOL.NFS })); public String getVolumeTypeStr() { return getVolumeTypeStr(getVolumeType()); @@ -106,9 +109,9 @@ public class Volume extends Entity { // TODO find a way to get the replica / strip count if (volumeType == VOLUME_TYPE.DISTRIBUTED_STRIPE) { setReplicaCount(0); - setStripeCount(3); + setStripeCount(DEFAULT_STRIPE_COUNT); } else if (volumeType == VOLUME_TYPE.DISTRIBUTED_MIRROR) { - setReplicaCount(2); + setReplicaCount(DEFAULT_REPLICA_COUNT); setStripeCount(0); } else { setReplicaCount(0); @@ -204,9 +207,6 @@ public class Volume extends Entity { } public void addDisk(String disk) { - // if (disks.add(disk) && disk.getStatus() != DISK_STATUS.OFFLINE) { - // totalDiskSpace += disk.getSpace(); - // } disks.add(disk); } @@ -217,9 +217,7 @@ public class Volume extends Entity { } public void removeDisk(String disk) { - // if (disks.remove(disk)) { - // totalDiskSpace -= disk.getSpace(); - // } + disks.remove(disk); } public void removeAllDisks() { @@ -227,6 +225,18 @@ public class Volume extends Entity { totalDiskSpace = 0; } + public void addBrick(String brick) { + bricks.add(brick); + } + + public void removeBrick(String brick) { + bricks.remove(brick); + } + + public List<String> getBricks() { + return bricks; + } + public void setDisks(List<String> disks) { removeAllDisks(); addDisks(disks); diff --git a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/adapters/VolumeLogDateAdapter.java b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/adapters/VolumeLogDateAdapter.java new file mode 100644 index 00000000..fac208ad --- /dev/null +++ b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/model/adapters/VolumeLogDateAdapter.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero 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 Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + *******************************************************************************/ + +package com.gluster.storage.management.core.model.adapters; + +import java.util.Date; + +import javax.xml.bind.annotation.adapters.XmlAdapter; + +import com.gluster.storage.management.core.utils.DateUtil; + +/** + * Adapter class used for converting timestamp from Gluster volume log files to Date object. + */ +public class VolumeLogDateAdapter extends XmlAdapter<String, Date> { + private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS"; + + /* (non-Javadoc) + * @see javax.xml.bind.annotation.adapters.XmlAdapter#unmarshal(java.lang.Object) + */ + @Override + public Date unmarshal(String input) { + input = input.trim(); + if(input.length() > DATE_FORMAT.length()) { + input = input.substring(0, DATE_FORMAT.length()); + } + return DateUtil.stringToDate(input, DATE_FORMAT); + } + + /* (non-Javadoc) + * @see javax.xml.bind.annotation.adapters.XmlAdapter#marshal(java.lang.Object) + */ + @Override + public String marshal(Date input) { + return DateUtil.dateToString(input, DATE_FORMAT); + } +} diff --git a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/response/LogMessageListResponse.java b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/response/LogMessageListResponse.java new file mode 100644 index 00000000..191334d3 --- /dev/null +++ b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/response/LogMessageListResponse.java @@ -0,0 +1,42 @@ +/** + * + */ +package com.gluster.storage.management.core.response; + +import java.util.ArrayList; +import java.util.List; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; + +import com.gluster.storage.management.core.model.LogMessage; +import com.gluster.storage.management.core.model.Status; + +@XmlRootElement(name = "response") +public class LogMessageListResponse extends AbstractResponse { + private List<LogMessage> logMessages = new ArrayList<LogMessage>(); + + public LogMessageListResponse() { + } + + public LogMessageListResponse(Status status, List<LogMessage> logMessages) { + setStatus(status); + setLogMessages(logMessages); + } + + @XmlElementWrapper(name = "logMessages") + @XmlElement(name = "logMessage", type = LogMessage.class) + public List<LogMessage> getLogMessages() { + return logMessages; + } + + public void setLogMessages(List<LogMessage> logMessages) { + this.logMessages = logMessages; + } + + @Override + public Object getData() { + return getLogMessages(); + } +} diff --git a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/response/VolumeListResponse.java b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/response/VolumeListResponse.java index fc1c9a6c..97085603 100644 --- a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/response/VolumeListResponse.java +++ b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/response/VolumeListResponse.java @@ -30,10 +30,6 @@ public class VolumeListResponse extends AbstractResponse { return this.volumes; } - /** - * @param volumes - * volumes to set - */ public void setVolumes(List<Volume> volumes) { this.volumes = volumes; } diff --git a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/utils/DateUtil.java b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/utils/DateUtil.java index 1b284cb8..8677fecd 100644 --- a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/utils/DateUtil.java +++ b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/utils/DateUtil.java @@ -18,18 +18,94 @@ *******************************************************************************/ package com.gluster.storage.management.core.utils; -import java.text.DateFormat; +import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; +import com.gluster.storage.management.core.constants.CoreConstants; +import com.gluster.storage.management.core.exceptions.GlusterRuntimeException; + public class DateUtil { + + /** + * Formats given date in pure date format (without time component) using default format + * {@link CoreConstants#PURE_DATE_FORMAT} + * + * @param inputDate + * Date to be formatted + * @return Formatted String representation of the given date + */ public static final String formatDate(Date inputDate) { - DateFormat formatter = new SimpleDateFormat("MM/dd/yyyy"); - return formatter.format(inputDate); + return dateToString(inputDate, CoreConstants.PURE_DATE_FORMAT); } - + + /** + * Formats given date in pure time format (without date component) using default format + * {@link CoreConstants#PURE_TIME_FORMAT} + * + * @param inputDate + * Date to be formatted + * @return Formatted String representation of the given date + */ public static final String formatTime(Date inputDate) { - DateFormat formatter = new SimpleDateFormat("HH:mm:ss z"); - return formatter.format(inputDate); + return dateToString(inputDate, CoreConstants.PURE_TIME_FORMAT); + } + + /** + * Converts given date object to string by formatting it in given format + * + * @param date + * Date to be formatted + * @param dateFormat + * Date format + * @return String representation of the given Date + */ + public static final String dateToString(Date date, String dateFormat) { + SimpleDateFormat dateFormatter = new SimpleDateFormat(dateFormat); + return dateFormatter.format(date); + } + + /** + * Converts given date object to string by formatting it using default date format + * {@link CoreConstants#DATE_WITH_TIME_FORMAT} + * + * @param date + * Date to be formatted + * @param dateFormat + * Date format + * @return String representation of the given Date + */ + public static final String dateToString(Date date) { + return dateToString(date, CoreConstants.DATE_WITH_TIME_FORMAT); + } + + /** + * Converts given string to date using the given date format + * + * @param input + * Input string + * @param dateFormat + * The date format to be used + * @return Date object + */ + public static final Date stringToDate(String input, String dateFormat) { + try { + SimpleDateFormat dateFormatter = new SimpleDateFormat(dateFormat); + return dateFormatter.parse(input); + } catch (ParseException e) { + throw new GlusterRuntimeException("Error trying to parse string [" + input + "] in to date using format [" + + dateFormat + "]", e); + } + } + + /** + * Converts given string to date using the default date format {@link CoreConstants#DATE_WITH_TIME_FORMAT} + * + * @param input + * Input string + * @return Date object + */ + public static final Date stringToDate(String input) { + return stringToDate(input, CoreConstants.DATE_WITH_TIME_FORMAT); } } diff --git a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/utils/GlusterCoreUtil.java b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/utils/GlusterCoreUtil.java new file mode 100644 index 00000000..9e3084fb --- /dev/null +++ b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/utils/GlusterCoreUtil.java @@ -0,0 +1,38 @@ +/** + * GlusterCoreUtil.java + * + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero 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 Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + */ +package com.gluster.storage.management.core.utils; + +import java.util.ArrayList; +import java.util.List; + +import com.gluster.storage.management.core.model.Disk; + + +public class GlusterCoreUtil { + // Convert from Disk list to Qualified disk name list + public static final List<String> getQualifiedDiskNames(List<Disk> diskList) { + List<String> qualifiedDiskNames = new ArrayList<String>(); + for (Disk disk : diskList) { + qualifiedDiskNames.add(disk.getQualifiedName()); + } + return qualifiedDiskNames; + } +} diff --git a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/utils/StringUtil.java b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/utils/StringUtil.java index 45f4c436..29026410 100644 --- a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/utils/StringUtil.java +++ b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/utils/StringUtil.java @@ -18,15 +18,33 @@ *******************************************************************************/ package com.gluster.storage.management.core.utils; +import java.util.ArrayList; +import java.util.List; + public class StringUtil { - public static boolean filterString(String sourceString, - String filterString, boolean caseSensitive) { - return caseSensitive ? sourceString.contains(filterString) - : sourceString.toLowerCase().contains( - filterString.toLowerCase()); + public static boolean filterString(String sourceString, String filterString, boolean caseSensitive) { + return caseSensitive ? sourceString.contains(filterString) : sourceString.toLowerCase().contains( + filterString.toLowerCase()); } - + public static String removeSpaces(String str) { return str.replaceAll("\\s+", ""); } + + public static String ListToString(List<String> list, String delimiter) { + StringBuilder output = new StringBuilder(); + for(String element : list) { + output.append(element).append(delimiter); + } + String outputStr = output.toString(); + return outputStr.substring(0, outputStr.length() - (delimiter.length()+1)); + } + + public static <T extends Enum<T>> List<String> enumToArray(T[] values) { + List<String> enumAsArray = new ArrayList<String>(); + for(T value : values) { + enumAsArray.add(value.toString()); + } + return enumAsArray; + } } diff --git a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/utils/ValidationUtil.java b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/utils/ValidationUtil.java new file mode 100644 index 00000000..b1ceb478 --- /dev/null +++ b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/utils/ValidationUtil.java @@ -0,0 +1,80 @@ +package com.gluster.storage.management.core.utils; + +import java.util.regex.Pattern; + +public class ValidationUtil { + + // Access control may contains IP with wild card(*), hostname and/or multiple ip/hostnames + public static boolean isValidAccessControl(String ac) { + String access[] = ac.split(","); + boolean isValidAccessControl = true; + for (int i = 0; i < access.length && isValidAccessControl; i++) { + isValidAccessControl = (isValidIpWithWC(access[i]) || isValidHostName(access[i])); + } + return isValidAccessControl; + } + + public static boolean isValidIpWithWC(String ip) { + String ipAddress[] = ip.split("\\."); + boolean isValid = true; + + if (ip.equals("0.0.0.0") || ip.equals("255.255.255.255")) { // Invalidate the special ip's + isValid = false; + } + + int iterator=ipAddress.length-1; + + if (ipAddress.length <= 4 && ipAddress[ipAddress.length - 1].equals("*")) { + iterator = ipAddress.length - 2; + } else if (ipAddress.length < 4 || ipAddress.length > 4 ){ + isValid = false; + iterator = ipAddress.length - 1; + } + + for (int i = 0; i <= iterator && isValid; i++) { + if (ipAddress[i].equals("*")) { + isValid = (i == ipAddress.length - 1) ? isValid : false; + } else { + isValid = isValidIpQuad(ipAddress[i]); + } + } + return isValid; + } + + public static boolean isValidIp(String ip) { + String ipAddress[] = ip.split("\\."); + boolean isValid = true; + + if (ip.equals("0.0.0.0") || ip.equals("255.255.255.255")) { // Invalidate the special ip's + isValid = false; + } + if (ipAddress.length < 4) { + isValid = false; + } + for (int i = 0; i < ipAddress.length && isValid; i++) { + isValid = isValidIpQuad(ipAddress[i]); + } + return isValid; + } + + private static boolean isValidIpQuad(String ipQuad) { + Pattern pattern = Pattern.compile("([01]?\\d\\d?|2[0-4]\\d|25[0-5])"); + return pattern.matcher(ipQuad).matches(); + } + + public static boolean isValidHostName(String hostName) { + Pattern pattern = Pattern + .compile("^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\\-]*[A-Za-z0-9])$"); + return pattern.matcher(hostName).matches(); + } + + public static void main(String[] argv) { + String ip = "0.0.0.0"; + // System.out.println("Is valid ip (" + ip + ")? " + isValidIp(ip)); + String hostName = "myhost.q"; + // System.out.println(isValidHostName(hostName)); + // System.out.println(isValidHostName(hostName)); + System.out.println(isValidAccessControl(hostName)); + } + +} diff --git a/src/com.gluster.storage.management.gui.feature/feature.xml b/src/com.gluster.storage.management.gui.feature/feature.xml index 2448b3cc..7f99803e 100644 --- a/src/com.gluster.storage.management.gui.feature/feature.xml +++ b/src/com.gluster.storage.management.gui.feature/feature.xml @@ -772,4 +772,11 @@ fragment="true" unpack="false"/> + <plugin + id="org.apache.commons.lang" + download-size="0" + install-size="0" + version="0.0.0" + unpack="false"/> + </feature> diff --git a/src/com.gluster.storage.management.gui/META-INF/MANIFEST.MF b/src/com.gluster.storage.management.gui/META-INF/MANIFEST.MF index 2e59c854..076a2805 100644 --- a/src/com.gluster.storage.management.gui/META-INF/MANIFEST.MF +++ b/src/com.gluster.storage.management.gui/META-INF/MANIFEST.MF @@ -21,7 +21,8 @@ Require-Bundle: org.eclipse.ui;bundle-version="3.6.1", org.eclipse.birt.chart.device.swt;bundle-version="2.6.1", com.ibm.icu;bundle-version="4.2.1", com.richclientgui.rcptoolbox;bundle-version="1.0.5", - org.eclipse.core.resources + org.eclipse.core.resources, + org.apache.commons.lang;bundle-version="2.3.0" Bundle-RequiredExecutionEnvironment: JavaSE-1.6 Bundle-ActivationPolicy: lazy Bundle-ClassPath: . diff --git a/src/com.gluster.storage.management.gui/icons/reset-options.png b/src/com.gluster.storage.management.gui/icons/reset-options.png Binary files differnew file mode 100644 index 00000000..7b93eb05 --- /dev/null +++ b/src/com.gluster.storage.management.gui/icons/reset-options.png diff --git a/src/com.gluster.storage.management.gui/plugin.xml b/src/com.gluster.storage.management.gui/plugin.xml index f5ab3db7..b208fc24 100644 --- a/src/com.gluster.storage.management.gui/plugin.xml +++ b/src/com.gluster.storage.management.gui/plugin.xml @@ -223,6 +223,12 @@ </command> <command categoryId="com.gluster.storage.management.gui.category" + description="Reset all options of a Volume" + id="com.gluster.storage.management.gui.commands.ResetVolumeOptions" + name="Reset Options"> + </command> + <command + categoryId="com.gluster.storage.management.gui.category" description="Rebalance Volume" id="com.gluster.storage.management.gui.commands.RebalanceVolume" name="Rebalance Volume"> @@ -245,6 +251,17 @@ id="com.gluster.storage.management.gui.commands.MigrateDisk" name="Migrate Disk"> </command> + <command + categoryId="com.gluster.storage.management.gui.category" + description="Remove Disk" + id="com.gluster.storage.management.gui.commands.RemoveDisk" + name="Remove Disk"> + </command> + <command + description="Add Disk" + id="com.gluster.storage.management.gui.commands.AddDisk" + name="Add Disk"> + </command> </extension> <extension point="org.eclipse.ui.bindings"> @@ -320,6 +337,11 @@ id="com.gluster.storage.management.gui.KeyConfig" name="Gluster"> </scheme> + <key + commandId="com.gluster.storage.management.gui.commands.ResetVolumeOptions" + schemeId="com.gluster.storage.management.gui.KeyConfig" + sequence="CTRL+SHIFT+O"> + </key> </extension> <extension id="product" @@ -480,11 +502,11 @@ style="push" toolbarPath="Normal" tooltip="Migrate Disk"> - <enablement> - <objectClass - name="com.gluster.storage.management.core.model.Disk"> - </objectClass> - </enablement> + <enablement> + <objectClass + name="com.gluster.storage.management.core.model.Disk"> + </objectClass> + </enablement> </action> <action allowLabelUpdate="false" @@ -504,6 +526,22 @@ </action> <action allowLabelUpdate="false" + class="com.gluster.storage.management.gui.actions.ResetVolumeOptionsAction" + definitionId="com.gluster.storage.management.gui.commands.ResetVolumeOptions" + icon="icons/reset-options.png" + id="com.gluster.storage.management.gui.actions.ResetVolumeOptionsAction" + label="Reset &Options" + menubarPath="com.gluster.storage.management.gui.menu.volume/volume" + mode="FORCE_TEXT" + pulldown="false" + retarget="false" + state="false" + style="push" + toolbarPath="Normal" + tooltip="Reset all options of the volume"> + </action> + <action + allowLabelUpdate="false" class="com.gluster.storage.management.gui.actions.RebalanceVolumeAction" definitionId="com.gluster.storage.management.gui.commands.RebalanceVolume" icon="icons/volume-rebalance.png" @@ -550,6 +588,38 @@ toolbarPath="Normal" tooltip="Start Volume"> </action> + <action + allowLabelUpdate="false" + class="com.gluster.storage.management.gui.actions.RemoveDiskAction" + definitionId="com.gluster.storage.management.gui.commands.RemoveDisk" + icon="icons/disk.png" + id="com.gluster.storage.management.gui.actions.RemoveDiskAction" + label="Remove Disk" + menubarPath="com.gluster.storage.management.gui.menu.volume/volume" + mode="FORCE_TEXT" + pulldown="false" + retarget="false" + state="false" + style="push" + toolbarPath="Normal" + tooltip="Remove Disk from Volume"> + </action> + <action + allowLabelUpdate="false" + class="com.gluster.storage.management.gui.actions.AddDiskAction" + definitionId="com.gluster.storage.management.gui.commands.AddDisk" + icon="icons/disk.png" + id="com.gluster.storage.management.gui.actions.AddDiskAction" + label="&Add Disk" + menubarPath="com.gluster.storage.management.gui.menu.volume/volume" + mode="FORCE_TEXT" + pulldown="false" + retarget="false" + state="false" + style="push" + toolbarPath="Normal" + tooltip="Add Disk to Volume"> + </action> <menu id="com.gluster.storage.management.gui.menu.volume" label="&Gluster" diff --git a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/Application.java b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/Application.java index 50dbd314..47f2cfd9 100644 --- a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/Application.java +++ b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/Application.java @@ -25,6 +25,7 @@ import java.util.List; import org.eclipse.core.databinding.observable.Realm; import org.eclipse.equinox.app.IApplication; import org.eclipse.equinox.app.IApplicationContext; +import org.eclipse.jface.action.IStatusLineManager; import org.eclipse.jface.databinding.swt.SWTObservables; import org.eclipse.jface.window.Window; import org.eclipse.swt.widgets.Display; @@ -43,6 +44,7 @@ public class Application implements IApplication { public static final String PLUGIN_ID = "com.gluster.storage.management.gui"; private static Application instance; private List<IEntityListener> entityListeners = Collections.synchronizedList(new ArrayList<IEntityListener>()); + private IStatusLineManager statusLineManager; public Application() { instance = this; @@ -52,6 +54,14 @@ public class Application implements IApplication { return instance; } + public IStatusLineManager getStatusLineManager() { + return statusLineManager; + } + + public void setStatusLineManager(IStatusLineManager statusLineManager) { + this.statusLineManager = statusLineManager; + } + private boolean login() { LoginDialog loginDialog = new LoginDialog(new Shell(Display.getDefault())); return (loginDialog.open() == Window.OK); diff --git a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/ApplicationWorkbenchWindowAdvisor.java b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/ApplicationWorkbenchWindowAdvisor.java index 722821f7..05d7443f 100644 --- a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/ApplicationWorkbenchWindowAdvisor.java +++ b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/ApplicationWorkbenchWindowAdvisor.java @@ -56,5 +56,7 @@ public class ApplicationWorkbenchWindowAdvisor extends WorkbenchWindowAdvisor { public void postWindowCreate() { super.postWindowCreate(); guiHelper.centerShellInScreen(getWindowConfigurer().getWindow().getShell()); + Application.getApplication().setStatusLineManager( + getWindowConfigurer().getActionBarConfigurer().getStatusLineManager()); } } diff --git a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/VolumeLogTableLabelProvider.java b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/VolumeLogTableLabelProvider.java index 399cdc65..9858a25b 100644 --- a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/VolumeLogTableLabelProvider.java +++ b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/VolumeLogTableLabelProvider.java @@ -19,19 +19,11 @@ package com.gluster.storage.management.gui; -import com.gluster.storage.management.core.model.Disk; import com.gluster.storage.management.core.model.LogMessage; import com.gluster.storage.management.core.utils.DateUtil; -import com.gluster.storage.management.gui.utils.GUIHelper; import com.gluster.storage.management.gui.views.details.VolumeLogsPage.LOG_TABLE_COLUMN_INDICES; public class VolumeLogTableLabelProvider extends TableLabelProviderAdapter { - private GUIHelper guiHelper = GUIHelper.getInstance(); - - private String getFormattedDiskName(Disk disk) { - return disk.getServerName() + ":" + disk.getName(); - } - @Override public String getColumnText(Object element, int columnIndex) { if (!(element instanceof LogMessage)) { @@ -41,7 +33,7 @@ public class VolumeLogTableLabelProvider extends TableLabelProviderAdapter { LogMessage logMessage = (LogMessage) element; return (columnIndex == LOG_TABLE_COLUMN_INDICES.DATE.ordinal() ? DateUtil.formatDate(logMessage.getTimestamp()) : columnIndex == LOG_TABLE_COLUMN_INDICES.TIME.ordinal() ? DateUtil.formatTime(logMessage.getTimestamp()) - : columnIndex == LOG_TABLE_COLUMN_INDICES.DISK.ordinal() ? getFormattedDiskName(logMessage.getDisk()) + : columnIndex == LOG_TABLE_COLUMN_INDICES.DISK.ordinal() ? logMessage.getDisk() : columnIndex == LOG_TABLE_COLUMN_INDICES.SEVERITY.ordinal() ? "" + logMessage.getSeverity() : columnIndex == LOG_TABLE_COLUMN_INDICES.MESSAGE.ordinal() ? logMessage.getMessage() : "Invalid"); } diff --git a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/actions/AbstractActionDelegate.java b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/actions/AbstractActionDelegate.java index adc98f98..1c5a3c72 100644 --- a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/actions/AbstractActionDelegate.java +++ b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/actions/AbstractActionDelegate.java @@ -73,7 +73,7 @@ public abstract class AbstractActionDelegate implements IWorkbenchWindowActionDe this.window = window; } - private Shell getShell() { + protected Shell getShell() { if(window == null) { return Display.getDefault().getActiveShell(); } diff --git a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/actions/AddDiskAction.java b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/actions/AddDiskAction.java new file mode 100644 index 00000000..cc57c541 --- /dev/null +++ b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/actions/AddDiskAction.java @@ -0,0 +1,84 @@ +/** + * AddDiskAction.java + * + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero 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 Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + */ + +package com.gluster.storage.management.gui.actions; + +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.wizard.WizardDialog; +import org.eclipse.swt.widgets.Display; + +import com.gluster.storage.management.client.GlusterDataModelManager; +import com.gluster.storage.management.core.model.Volume; +import com.gluster.storage.management.gui.dialogs.AddDiskWizard; + +public class AddDiskAction extends AbstractActionDelegate { + private Volume volume; + private GlusterDataModelManager modelManager = GlusterDataModelManager.getInstance(); + + /* (non-Javadoc) + * @see org.eclipse.ui.IWorkbenchWindowActionDelegate#dispose() + */ + @Override + public void dispose() { + window = null; + } + + /* (non-Javadoc) + * @see com.gluster.storage.management.gui.actions.AbstractActionDelegate#performAction(org.eclipse.jface.action.IAction) + */ + @Override + protected void performAction(IAction action) { + //TODO: open a dialog box + // MessageDialog.openInformation(getShell(), "Action captured", action.getDescription() + "\n" + volume.getName()); + Display.getDefault().asyncExec(new Runnable() { + + @Override + public void run() { + AddDiskWizard wizard = new AddDiskWizard(volume); // Also add single page + + WizardDialog dialog = new WizardDialog(getShell(), wizard); + dialog.create(); + dialog.getShell().setSize(1024, 600); + dialog.open(); + } + }); + } + + + /* + * (non-Javadoc) + * + * @see + * com.gluster.storage.management.gui.actions.AbstractActionDelegate#selectionChanged(org.eclipse.jface.action.IAction + * , org.eclipse.jface.viewers.ISelection) + */ + @Override + public void selectionChanged(IAction action, ISelection selection) { + super.selectionChanged(action, selection); + + if (selectedEntity instanceof Volume) { + this.volume = (Volume) selectedEntity; + // action.setEnabled(volume.getStatus() == VOLUME_STATUS.ONLINE); + } + } + +} diff --git a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/actions/CreateVolumeAction.java b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/actions/CreateVolumeAction.java index 9ec500bc..7f06bc96 100644 --- a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/actions/CreateVolumeAction.java +++ b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/actions/CreateVolumeAction.java @@ -36,7 +36,7 @@ public class CreateVolumeAction extends AbstractActionDelegate { public void run() { CreateVolumeWizard wizard = new CreateVolumeWizard(); - WizardDialog dialog = new WizardDialog(window.getShell(), wizard); + WizardDialog dialog = new WizardDialog(getShell(), wizard); dialog.create(); dialog.getShell().setSize(500, 550); dialog.open(); diff --git a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/actions/DeleteVolumeAction.java b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/actions/DeleteVolumeAction.java index 82ac1663..d5501082 100644 --- a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/actions/DeleteVolumeAction.java +++ b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/actions/DeleteVolumeAction.java @@ -19,15 +19,80 @@ package com.gluster.storage.management.gui.actions; import org.eclipse.jface.action.IAction; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.viewers.ISelection; + +import com.gluster.storage.management.client.GlusterDataModelManager; +import com.gluster.storage.management.client.VolumesClient; +import com.gluster.storage.management.core.model.Status; +import com.gluster.storage.management.core.model.Volume; +import com.gluster.storage.management.core.model.Volume.VOLUME_STATUS; +import com.gluster.storage.management.gui.IImageKeys; +import com.gluster.storage.management.gui.utils.GUIHelper; public class DeleteVolumeAction extends AbstractActionDelegate { + private Volume volume; + private GlusterDataModelManager modelManager = GlusterDataModelManager.getInstance(); + @Override protected void performAction(IAction action) { - System.out.println("Running [" + this.getClass().getSimpleName() + "]"); + final String actionDesc = action.getDescription(); + + String warningMessage; + if (volume.getStatus() == VOLUME_STATUS.OFFLINE) { + warningMessage = "Are you sure to delete the Volume[" + volume.getName() + "] ?"; + } else { + warningMessage = "Volume [" + volume.getName() + "] is online, \nAre you sure to continue?"; + } + + Integer deleteOption = new MessageDialog(getShell(), "Delete Volume", GUIHelper.getInstance().getImage( + IImageKeys.VOLUME), warningMessage, MessageDialog.QUESTION, new String[] { "Cancel", + "Delete volume and it's data", "Delete volume, keep back-up of data" }, 2).open(); + if (deleteOption == 0) { + return; + } + + VolumesClient client = new VolumesClient(modelManager.getSecurityToken()); + + Status status; + if (volume.getStatus() == VOLUME_STATUS.ONLINE) { // To stop the volume service, if running + status = client.stopVolume(volume.getName()); + if (!status.isSuccess()) { + showErrorDialog(actionDesc, "Volume [" + volume.getName() + "] could not be stopped! Error: [" + status + + "]"); + return; + } + } + String confirmDelete = ""; + if (deleteOption == 1) { + confirmDelete = "-d"; + } + + status = client.deleteVolume(volume, confirmDelete); + if (status.isSuccess()) { + showInfoDialog(actionDesc, "Volume [" + volume.getName() + "] deleted successfully!"); + modelManager.deleteVolume(volume); + } else { + if (status.isPartSuccess()) { + showWarningDialog(actionDesc, "Volume deleted, but following error(s) occured: " + status); + modelManager.deleteVolume(volume); + } else { + showErrorDialog(actionDesc, + "Volume [" + volume.getName() + "] could not be deleted! Error: [" + status + "]"); + } + } } @Override public void dispose() { System.out.println("Disposing [" + this.getClass().getSimpleName() + "]"); } + + @Override + public void selectionChanged(IAction action, ISelection selection) { + super.selectionChanged(action, selection); + if (selectedEntity instanceof Volume) { + volume = (Volume) selectedEntity; + } + } } diff --git a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/actions/IActionSetIDs.java b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/actions/IActionConstants.java index 6c54bae8..2f696709 100644 --- a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/actions/IActionSetIDs.java +++ b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/actions/IActionConstants.java @@ -18,7 +18,7 @@ *******************************************************************************/ package com.gluster.storage.management.gui.actions; -public interface IActionSetIDs { +public interface IActionConstants { public static final String ACTION_SET_CLUSTER = "com.gluster.storage.management.gui.actionsets.gluster"; public static final String ACTION_SET_VOLUMES = "com.gluster.storage.management.gui.actionsets.volumes"; public static final String ACTION_SET_VOLUME = "com.gluster.storage.management.gui.actionsets.volume"; @@ -30,4 +30,7 @@ public interface IActionSetIDs { public static final String ACTION_SET_DISCOVERED_SERVER = "com.gluster.storage.management.gui.actionsets.serverdiscovered"; public static final String ACTION_SET_EDIT = "com.gluster.storage.management.gui.actionsets.edit"; + + public static final String COMMAND_CREATE_VOLUME = "com.gluster.storage.management.gui.commands.CreateVolume"; + public static final String COMMAND_ADD_SERVER = "com.gluster.storage.management.gui.commands.AddServer"; } diff --git a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/actions/RemoveDiskAction.java b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/actions/RemoveDiskAction.java new file mode 100644 index 00000000..19cf84f7 --- /dev/null +++ b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/actions/RemoveDiskAction.java @@ -0,0 +1,65 @@ +package com.gluster.storage.management.gui.actions; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.ui.IWorkbenchPart; + +import com.gluster.storage.management.client.GlusterDataModelManager; +import com.gluster.storage.management.client.VolumesClient; +import com.gluster.storage.management.core.model.Disk; +import com.gluster.storage.management.core.model.Entity; +import com.gluster.storage.management.core.model.Volume; +import com.gluster.storage.management.gui.utils.GUIHelper; +import com.gluster.storage.management.gui.views.VolumeDisksView; + +public class RemoveDiskAction extends AbstractActionDelegate { + private GlusterDataModelManager modelManager = GlusterDataModelManager.getInstance(); + private GUIHelper guiHelper = GUIHelper.getInstance(); + private List<Disk> disks; + + @Override + protected void performAction(IAction action) { + VolumesClient client = new VolumesClient(modelManager.getSecurityToken()); + // final Status status = client.removeDisk(); + } + + @Override + public void dispose() { + } + + @Override + public void selectionChanged(IAction action, ISelection selection) { + super.selectionChanged(action, selection); + + action.setEnabled(false); + Volume selectedVolume = (Volume)guiHelper.getSelectedEntity(window, Volume.class); + if (selectedVolume != null) { + // a volume is selected on navigation tree. Let's check if the currently open view is volume disks view + IWorkbenchPart view = guiHelper.getActiveView(); + if(view instanceof VolumeDisksView) { + // volume disks view is open. check if any disk is selected + disks = getSelectedDisks(selection); + action.setEnabled(disks.size() > 0); + } + } + } + + private List<Disk> getSelectedDisks(ISelection selection) { + List<Disk> selectedDisks = new ArrayList<Disk>(); + if (selection instanceof IStructuredSelection) { + Iterator<Object> iter = ((IStructuredSelection) selection).iterator(); + while (iter.hasNext()) { + Object selectedObj = iter.next(); + if (selectedObj instanceof Disk) { + selectedDisks.add((Disk)selectedObj); + } + } + } + return selectedDisks; + } +}
\ No newline at end of file diff --git a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/actions/ResetVolumeOptionsAction.java b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/actions/ResetVolumeOptionsAction.java new file mode 100644 index 00000000..7fd77ea8 --- /dev/null +++ b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/actions/ResetVolumeOptionsAction.java @@ -0,0 +1,64 @@ +package com.gluster.storage.management.gui.actions; + +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.IWorkbenchWindowActionDelegate; + +import com.gluster.storage.management.client.GlusterDataModelManager; +import com.gluster.storage.management.client.VolumesClient; +import com.gluster.storage.management.core.model.Status; +import com.gluster.storage.management.core.model.Volume; +import com.gluster.storage.management.core.model.Volume.VOLUME_STATUS; + +public class ResetVolumeOptionsAction extends AbstractActionDelegate { + private Volume volume; + private GlusterDataModelManager modelManager = GlusterDataModelManager.getInstance(); + + @Override + public void dispose() { + // TODO Auto-generated method stub + + } + + @Override + protected void performAction(IAction action) { + final String actionDesc = action.getDescription(); + + boolean confirmed = showConfirmDialog(actionDesc, + "Are you sure you want to reset all options of the volume [" + volume.getName() + "] ?"); + if (!confirmed) { + return; + } + + final Status status = resetVolumeOptions(); + if (status.isSuccess()) { + showInfoDialog(actionDesc, "Volume options for [" + volume.getName() + "] reset successfully!"); + modelManager.resetVolumeOptions(volume); + } else { + showErrorDialog(actionDesc, "Volume options for [" + volume.getName() + "] could not be reset! Error: [" + status + + "]"); + } + } + + private Status resetVolumeOptions() { + return new VolumesClient(modelManager.getSecurityToken()).resetVolumeOptions(volume.getName()); + } + + /* + * (non-Javadoc) + * + * @see + * com.gluster.storage.management.gui.actions.AbstractActionDelegate#selectionChanged(org.eclipse.jface.action.IAction + * , org.eclipse.jface.viewers.ISelection) + */ + @Override + public void selectionChanged(IAction action, ISelection selection) { + super.selectionChanged(action, selection); + + if (selectedEntity instanceof Volume) { + volume = (Volume) selectedEntity; + action.setEnabled(volume.getOptions().size() > 0); + } + } +} diff --git a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/dialogs/AddDiskPage.java b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/dialogs/AddDiskPage.java new file mode 100644 index 00000000..7eb107c0 --- /dev/null +++ b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/dialogs/AddDiskPage.java @@ -0,0 +1,165 @@ +/** + * AddDiskPage.java + * + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero 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 Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + */ +package com.gluster.storage.management.gui.dialogs; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; + +import com.gluster.storage.management.client.GlusterDataModelManager; +import com.gluster.storage.management.core.model.Disk; +import com.gluster.storage.management.core.model.Volume; +import com.gluster.storage.management.core.model.Volume.VOLUME_TYPE; +import com.richclientgui.toolbox.duallists.DualListComposite.ListContentChangedListener; +import com.richclientgui.toolbox.duallists.IRemovableContentProvider; + +/** + * @author root + * + */ +public class AddDiskPage extends WizardPage { + private List<Disk> availableDisks = new ArrayList<Disk>(); + private List<Disk> selectedDisks = new ArrayList<Disk>(); + private Volume volume = null; + private DisksSelectionPage page = null; + + + public static final String PAGE_NAME = "add.disk.volume.page"; + + /** + * @param pageName + */ + protected AddDiskPage(Volume volume) { + super(PAGE_NAME); + this.volume = volume; + setTitle("Add Disk"); + + String description = "Add disks to the Volume by choosing disks from the cluster servers.\n"; + if ( volume.getVolumeType() == VOLUME_TYPE.DISTRIBUTED_MIRROR) { + description += "(Disk selection should be multiples of " + volume.getReplicaCount() + ")"; + } else if (volume.getVolumeType() == VOLUME_TYPE.DISTRIBUTED_STRIPE) { + description += "(Disk selection should be multiples of " + volume.getStripeCount() + ")"; + } + setDescription(description); + + availableDisks = getAvailableDisks(volume); + + setPageComplete(false); + setErrorMessage("Please select disks to be added to the volume."); + } + + + private boolean isDiskUsed(Volume volume, Disk disk){ + for (String volumeDisk : volume.getDisks()) { // expected form of volumeDisk is "server:diskName" + if ( disk.getQualifiedName().equals(volumeDisk)) { + return true; + } + } + return false; + } + + protected List<Disk> getAvailableDisks(Volume volume) { + List<Disk> availableDisks = new ArrayList<Disk>(); + for (Disk disk : GlusterDataModelManager.getInstance().getReadyDisksOfAllServers()) { + if ( ! isDiskUsed(volume, disk) ) { + availableDisks.add(disk); + } + } + return availableDisks; + } + + + public List<Disk> getChosenDisks( ) { + return page.getChosenDisks(); + } + + private boolean isValidDiskSelection(int diskCount) { + if ( diskCount == 0) { + return false; + } + switch (volume.getVolumeType()) { + case DISTRIBUTED_MIRROR: + return (diskCount % volume.getReplicaCount() == 0); + case DISTRIBUTED_STRIPE: + return (diskCount % volume.getStripeCount() == 0); + } + return true; + } + + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite) + */ + @Override + public void createControl(Composite parent) { + getShell().setText("Add Disk"); + List<Disk> chosenDisks = new ArrayList<Disk>(); // or volume.getDisks(); + + page = new DisksSelectionPage(parent, SWT.NONE, availableDisks, chosenDisks); + page.addDiskSelectionListener(new ListContentChangedListener<Disk>() { + @Override + public void listContentChanged(IRemovableContentProvider<Disk> contentProvider) { + List<Disk> newChosenDisks = page.getChosenDisks(); + + // validate chosen disks + if(isValidDiskSelection(newChosenDisks.size())) { + clearError(); + } else { + setError(); + } + } + }); + setControl(page); + } + + private void setError() { + String errorMessage = null; + if ( volume.getVolumeType() == VOLUME_TYPE.PLAIN_DISTRIBUTE) { + errorMessage = "Please select at least one disk!"; + } else if( volume.getVolumeType() == VOLUME_TYPE.DISTRIBUTED_MIRROR) { + errorMessage = "Please select disks in multiples of " + volume.getReplicaCount(); + } else { + errorMessage = "Please select disks in multiples of " + volume.getStripeCount(); + } + + setPageComplete(false); + setErrorMessage(errorMessage); + } + + private void clearError() { + setErrorMessage(null); + setPageComplete(true); + } + + public DisksSelectionPage getDialogPage() { + return this.page; + } + + public void setPageComplete() { + + } + +} diff --git a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/dialogs/AddDiskWizard.java b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/dialogs/AddDiskWizard.java new file mode 100644 index 00000000..e9608e38 --- /dev/null +++ b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/dialogs/AddDiskWizard.java @@ -0,0 +1,92 @@ +/** + * AddDiskWizard.java + * + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero 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 Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + */ +package com.gluster.storage.management.gui.dialogs; + + +import java.util.List; + +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.wizard.Wizard; + +import com.gluster.storage.management.client.GlusterDataModelManager; +import com.gluster.storage.management.client.VolumesClient; +import com.gluster.storage.management.core.model.Disk; +import com.gluster.storage.management.core.model.Status; +import com.gluster.storage.management.core.model.Volume; +import com.gluster.storage.management.core.utils.GlusterCoreUtil; + + +/** + * + */ +public class AddDiskWizard extends Wizard { + private AddDiskPage page; + private Volume volume; + + + public AddDiskWizard(Volume volume) { + setWindowTitle("Gluster Management Console - Add disk"); + setHelpAvailable(false); // TODO: Introduce wizard help + this.volume = volume; + } + + public void addPages() { + page = new AddDiskPage(volume); + addPage(page); + } + + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.wizard.Wizard#performFinish() + */ + @Override + public boolean performFinish() { + List<Disk> disks = page.getChosenDisks(); + VolumesClient volumeClient = new VolumesClient(GlusterDataModelManager.getInstance().getSecurityToken()); + try { + Status status = volumeClient.addDisks(volume.getName(), disks); + if (!status.isSuccess()) { + MessageDialog.openError(getShell(), "Add disk(s) to Volume", status.getMessage()); + return status.isSuccess(); + } else { + volume.addDisks(GlusterCoreUtil.getQualifiedDiskNames(disks)); + MessageDialog.openInformation(getShell(), "Add disk(s) to Volume", "Disk(s) are successfully added to " + + volume.getName()); + return status.isSuccess(); + } + } catch (Exception e) { + MessageDialog.openError(getShell(), "Add disk(s) to Volume", e.getMessage()); + return false; + } + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.wizard.Wizard#canFinish() + */ + @Override + public boolean canFinish() { + return super.canFinish() && page.isPageComplete(); + } +} diff --git a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/dialogs/CreateVolumePage1.java b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/dialogs/CreateVolumePage1.java index b09bbb44..e319dcca 100644 --- a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/dialogs/CreateVolumePage1.java +++ b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/dialogs/CreateVolumePage1.java @@ -25,7 +25,6 @@ import java.util.Set; import org.eclipse.jface.viewers.ArrayContentProvider; import org.eclipse.jface.viewers.ComboViewer; -import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.window.Window; @@ -51,6 +50,7 @@ import com.gluster.storage.management.core.model.Volume; import com.gluster.storage.management.core.model.Volume.NAS_PROTOCOL; import com.gluster.storage.management.core.model.Volume.TRANSPORT_TYPE; import com.gluster.storage.management.core.model.Volume.VOLUME_TYPE; +import com.gluster.storage.management.core.utils.ValidationUtil; public class CreateVolumePage1 extends WizardPage { public static final String PAGE_NAME = "create.volume.page.1"; @@ -62,7 +62,6 @@ public class CreateVolumePage1 extends WizardPage { private Button btnNfs; private Button btnStartVolume; private Link linkCustomize; - private ValidationListener valListener = new ValidationListener(); /** * Create the wizard. @@ -85,123 +84,102 @@ public class CreateVolumePage1 extends WizardPage { return disks; } - private class ValidationListener implements ModifyListener { - /* (non-Javadoc) - * @see org.eclipse.swt.events.ModifyListener#modifyText(org.eclipse.swt.events.ModifyEvent) - */ - @Override - public void modifyText(ModifyEvent e) { - String volumeName = txtName.getText().trim(); - String accessControl = txtAccessControl.getText().trim(); - String volumeNameToken = "^[a-zA-Z][a-zA-Z0-9\\-]*"; - - - setErrorMessage(null); - setPageComplete(true); - - if(volumeName.length() == 0) { - setPageComplete(false); - setErrorMessage("Please enter Volume Name"); - } - - if (!volumeName.matches(volumeNameToken)) { - setPageComplete(false); - setErrorMessage("Please enter valid Volume Name"); - } - - if(accessControl.length() == 0) { - setPageComplete(false); - setErrorMessage("Please enter Access Control"); - } - - - // acl validation - String[] aclList = accessControl.split(","); - for (String ip : aclList) { - if (!isValidIP(ip)) { - setPageComplete(false); - setErrorMessage("Please enter valid access control list"); - } - } - - } - - private Boolean isValidIP(String ip) { - // String pattern = "^.[0-9]{1,3}/..[0-9]{1,3}/..[0-9]{1,3}/..[0-9]{1,3}"; - String pattern = "^.[0-9]{1,3}/."; - if (ip == "*") { - return true; - } - String[] ipQuads = ip.split("."); - for (String quad : ipQuads) { - if (!quad.matches(pattern)) { - return false; - } - } - return true; - - } - } - /** * Create contents of the wizard. * @param parent */ public void createControl(Composite parent) { - setPageComplete(false); - Composite container = new Composite(parent, SWT.NULL); - - setControl(container); - GridLayout gl_container = new GridLayout(2, false); - gl_container.verticalSpacing = 10; - gl_container.marginHeight = 10; - gl_container.marginLeft = 20; - gl_container.horizontalSpacing = 10; - container.setLayout(gl_container); + Composite container = createContainer(parent); - new Label(container, SWT.NONE); - new Label(container, SWT.NONE); + createEmptyRow(container); - Label lblName = new Label(container, SWT.NONE); - lblName.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); - lblName.setText("Name: "); + createNameLabel(container); + createNameText(container); - txtName = new Text(container, SWT.BORDER); - GridData txtNameData = new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1); - txtNameData.widthHint = 300; - txtName.setLayoutData(txtNameData); - txtName.addModifyListener(valListener); + createTypeLabel(container); + createTypeCombo(container); - Label lblType = new Label(container, SWT.NONE); - lblType.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); - lblType.setText("Type: "); + createTransportTypeLabel(container); + createTransportTypeValueLabel(container); - typeComboViewer = new ComboViewer(container, SWT.READ_ONLY); - Combo typeCombo = typeComboViewer.getCombo(); - GridData typeComboData = new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1); - typeCombo.setLayoutData(typeComboData); - typeComboViewer.setContentProvider(new ArrayContentProvider()); - typeComboViewer.setInput(Volume.VOLUME_TYPE.values()); - typeCombo.select(VOLUME_TYPE.PLAIN_DISTRIBUTE.ordinal()); // default type = Plain Distribute - typeComboViewer.setLabelProvider(new LabelProvider() { - @Override - public String getText(Object element) { - VOLUME_TYPE volumeType = (VOLUME_TYPE)element; - return Volume.getVolumeTypeStr(volumeType); - } - }); + createDisksLabel(container); + createDisksCustomizeLink(container); - Label lblTransportType = new Label(container, SWT.NONE); - lblTransportType.setText("Transport Type: "); + createNasProtocolLabel(container); + createNasProtocolCheckboxes(container); - Label lblEthernet = new Label(container, SWT.NONE); - lblEthernet.setText("Ethernet"); + createAccessControlLabel(container); + createAccessControlText(container); + + createEmptyLabel(container); + createAccessControlInfoLabel(container); - Label lblDisks = new Label(container, SWT.RIGHT); - lblDisks.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); - lblDisks.setText("Disks: "); + createStartVolumeLabel(container); + createStartVolumeCheckbox(container); + } + + private void createStartVolumeCheckbox(Composite container) { + btnStartVolume = new Button(container, SWT.CHECK); + btnStartVolume.setSelection(true); + } + + private void createStartVolumeLabel(Composite container) { + Label lblStartVolume = new Label(container, SWT.NONE); + lblStartVolume.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + lblStartVolume.setText("Start Volume: "); + } + + private void createAccessControlInfoLabel(Composite container) { + Label lblAccessControlInfo = new Label(container, SWT.TOP); + lblAccessControlInfo.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 1, 1)); + lblAccessControlInfo.setText("(Comma separated list of IP addresses/hostnames)"); + } + + private void createEmptyLabel(Composite container) { + new Label(container, SWT.NONE); + } + + private void createAccessControlText(Composite container) { + txtAccessControl = new Text(container, SWT.BORDER); + txtAccessControl.setText("*"); + GridData accessControlData = new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1); + accessControlData.widthHint = 300; + txtAccessControl.setLayoutData(accessControlData); + txtAccessControl.addModifyListener(new ModifyListener() { + @Override + public void modifyText(ModifyEvent e) { + validateForm(); + } + }); + } + + private void createAccessControlLabel(Composite container) { + Label lblAccessControl = new Label(container, SWT.NONE); + lblAccessControl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + lblAccessControl.setText("Access Control: "); + } + + private void createNasProtocolCheckboxes(Composite container) { + Button btnGluster = new Button(container, SWT.CHECK); + btnGluster.setEnabled(false); + btnGluster.setSelection(true); + btnGluster.setText("Gluster"); + createEmptyLabel(container); + btnNfs = new Button(container, SWT.CHECK); + btnNfs.setEnabled(false); + btnNfs.setSelection(true); + btnNfs.setText("NFS"); + } + + private void createNasProtocolLabel(Composite container) { + Label lblNasProtocol = new Label(container, SWT.RIGHT); + lblNasProtocol.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + lblNasProtocol.setText("NAS Protocol: "); + } + + private void createDisksCustomizeLink(Composite container) { linkCustomize = new Link(container, SWT.UNDERLINE_LINK); linkCustomize.setText("All Disk(s) (<a>customize</a>)"); linkCustomize.addListener (SWT.Selection, new Listener () { @@ -222,43 +200,82 @@ public class CreateVolumePage1 extends WizardPage { }); } }); - - Label lblNasProtocol = new Label(container, SWT.RIGHT); - lblNasProtocol.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); - lblNasProtocol.setText("NAS Protocol: "); - - Button btnGluster = new Button(container, SWT.CHECK); - btnGluster.setEnabled(false); - btnGluster.setSelection(true); - btnGluster.setText("Gluster"); - new Label(container, SWT.NONE); - - btnNfs = new Button(container, SWT.CHECK); - btnNfs.setSelection(true); - btnNfs.setText("NFS"); - - Label lblAccessControl = new Label(container, SWT.NONE); - lblAccessControl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); - lblAccessControl.setText("Access Control: "); - - txtAccessControl = new Text(container, SWT.BORDER); - txtAccessControl.setText("*"); - GridData accessControlData = new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1); - accessControlData.widthHint = 300; - txtAccessControl.setLayoutData(accessControlData); - txtAccessControl.addModifyListener(valListener); + } - new Label(container, SWT.NONE); - Label lblAccessControlInfo = new Label(container, SWT.TOP); - lblAccessControlInfo.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 1, 1)); - lblAccessControlInfo.setText("(Comma separated list of IP addresses)"); - - Label lblStartVolume = new Label(container, SWT.NONE); - lblStartVolume.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); - lblStartVolume.setText("Start Volume: "); - - btnStartVolume = new Button(container, SWT.CHECK); - btnStartVolume.setSelection(true); + private void createDisksLabel(Composite container) { + Label lblDisks = new Label(container, SWT.RIGHT); + lblDisks.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + lblDisks.setText("Disks: "); + } + + private void createTransportTypeValueLabel(Composite container) { + Label lblEthernet = new Label(container, SWT.NONE); + lblEthernet.setText("Ethernet"); + } + + private void createTransportTypeLabel(Composite container) { + Label lblTransportType = new Label(container, SWT.NONE); + lblTransportType.setText("Transport Type: "); + } + + private void createTypeCombo(Composite container) { + typeComboViewer = new ComboViewer(container, SWT.READ_ONLY); + Combo typeCombo = typeComboViewer.getCombo(); + GridData typeComboData = new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1); + typeCombo.setLayoutData(typeComboData); + typeComboViewer.setContentProvider(new ArrayContentProvider()); + typeComboViewer.setInput(Volume.VOLUME_TYPE.values()); + typeCombo.select(VOLUME_TYPE.PLAIN_DISTRIBUTE.ordinal()); // default type = Plain Distribute + typeComboViewer.setLabelProvider(new LabelProvider() { + @Override + public String getText(Object element) { + VOLUME_TYPE volumeType = (VOLUME_TYPE)element; + return Volume.getVolumeTypeStr(volumeType); + } + }); + } + + private void createTypeLabel(Composite container) { + Label lblType = new Label(container, SWT.NONE); + lblType.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + lblType.setText("Type: "); + } + + private void createNameText(Composite container) { + txtName = new Text(container, SWT.BORDER); + GridData txtNameData = new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1); + txtNameData.widthHint = 300; + txtName.setLayoutData(txtNameData); + txtName.addModifyListener(new ModifyListener() { + @Override + public void modifyText(ModifyEvent e) { + validateForm(); + } + }); + } + + private void createNameLabel(Composite container) { + Label lblName = new Label(container, SWT.NONE); + lblName.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + lblName.setText("Name: "); + } + + private void createEmptyRow(Composite container) { + createEmptyLabel(container); + createEmptyLabel(container); + } + + private Composite createContainer(Composite parent) { + Composite container = new Composite(parent, SWT.NULL); + setControl(container); + + GridLayout gl_container = new GridLayout(2, false); + gl_container.verticalSpacing = 10; + gl_container.marginHeight = 10; + gl_container.marginLeft = 20; + gl_container.horizontalSpacing = 10; + container.setLayout(gl_container); + return container; } public Volume getVolume() { @@ -270,28 +287,86 @@ public class CreateVolumePage1 extends WizardPage { volume.setTransportType(TRANSPORT_TYPE.ETHERNET); Set<NAS_PROTOCOL> nasProtocols = new HashSet<Volume.NAS_PROTOCOL>(); nasProtocols.add(NAS_PROTOCOL.GLUSTERFS); - if(btnNfs.getSelection()) { - nasProtocols.add(NAS_PROTOCOL.NFS); - } + nasProtocols.add(NAS_PROTOCOL.NFS); volume.setAccessControlList(txtAccessControl.getText()); return volume; } - public Boolean getStartVolumeRequest() { + public Boolean startVolumeAfterCreation() { return btnStartVolume.getSelection(); } - public Boolean isValidCreateVolumeForm() { - IStructuredSelection selection = (IStructuredSelection)typeComboViewer.getSelection(); - if (selection.getFirstElement().equals(VOLUME_TYPE.DISTRIBUTED_MIRROR) && ((int)volume.getDisks().size()) % 2 != 0 ) { - setErrorMessage("Mirror type volume requires disk in multiples of two"); - return false; - } else if(selection.getFirstElement().equals(VOLUME_TYPE.DISTRIBUTED_STRIPE) && ((int)volume.getDisks().size()) % 4 != 0) { - setErrorMessage("Stripe type volume requires disk in multiples of four"); - return false; + public Boolean volumeExists(String volumeName) { + List<Volume> volumes = GlusterDataModelManager.getInstance().getModel().getCluster().getVolumes(); + for (Volume volume : volumes) { + if (volume.getName().equals(volumeName)) { + setErrorMessage("Volume name already exists."); + return false; + } } return true; } + + private void validateForm() { + clearErrors(); + validateVolumeName(); + validateAccessControl(); + validateDisks(); + } + + private void validateDisks() { + int diskCount = volume.getDisks().size(); + + if(diskCount < 1) { + setError("At least one disk must be selected!"); + } + + VOLUME_TYPE volumeType = (VOLUME_TYPE) ((IStructuredSelection) typeComboViewer + .getSelection()).getFirstElement(); + if (volumeType == VOLUME_TYPE.DISTRIBUTED_MIRROR && diskCount % 2 != 0) { + setError("Mirror type volume requires disks in multiples of two"); + } else if (volumeType == VOLUME_TYPE.DISTRIBUTED_STRIPE && diskCount % 4 != 0) { + setError("Stripe type volume requires disks in multiples of four"); + } + } + + private void validateAccessControl() { + String accessControl = txtAccessControl.getText().trim(); + if (accessControl.length() == 0) { + setError("Please enter Access Control"); + return; + } + + if (!ValidationUtil.isValidAccessControl(accessControl)) { + setError("Access control list must be a comma separated list of IP addresses/Host names. Please enter a valid value!"); + } + } + + private void validateVolumeName() { + String volumeName = txtName.getText().trim(); + String volumeNameToken = "^[a-zA-Z][a-zA-Z0-9\\-]*"; + if(volumeName.length() == 0) { + setError("Please enter Volume Name"); + } + + if (!volumeName.matches(volumeNameToken)) { + setError("Please enter valid Volume Name"); + } + + if(!volumeExists(volumeName)) { + setError("Volume [" + volumeName + "] already exists!"); + } + } + + private void clearErrors() { + setErrorMessage(null); + setPageComplete(true); + } + + private void setError(String errorMsg) { + setPageComplete(false); + setErrorMessage(errorMsg); + } } diff --git a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/dialogs/CreateVolumeWizard.java b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/dialogs/CreateVolumeWizard.java index 36755bec..66c26a89 100644 --- a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/dialogs/CreateVolumeWizard.java +++ b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/dialogs/CreateVolumeWizard.java @@ -38,33 +38,46 @@ public class CreateVolumeWizard extends Wizard { public void addPages() { addPage(new CreateVolumePage1()); } - + @Override public boolean performFinish() { + String dialogTitle = "Create Volume"; CreateVolumePage1 page = (CreateVolumePage1) getPage(CreateVolumePage1.PAGE_NAME); - if (!page.isValidCreateVolumeForm()) { - return false; - } - + Volume newVolume = page.getVolume(); GlusterDataModelManager modelManager = GlusterDataModelManager.getInstance(); VolumesClient volumesClient = new VolumesClient(modelManager.getSecurityToken()); Status status = volumesClient.createVolume(newVolume); - + if (status.isSuccess()) { + String message = "Volume created successfully!"; + boolean warning = false; newVolume.setStatus(VOLUME_STATUS.OFFLINE); - if (page.getStartVolumeRequest()) { + if (page.startVolumeAfterCreation()) { Status volumeStartStatus = volumesClient.startVolume(newVolume.getName()); if (volumeStartStatus.isSuccess()) { newVolume.setStatus(VOLUME_STATUS.ONLINE); + message = "Volume created and started successfully!"; + } else { + message = "Volume created successfuly, but couldn't be started. Error: " + volumeStartStatus; + warning = true; } } - //update the model - modelManager.addVolume(newVolume); - MessageDialog.openInformation(getShell(), "Create Volume", "Volume created successfully and configuration added!"); + + // update the model + modelManager.addVolume(newVolume); + if (warning) { + MessageDialog.openWarning(getShell(), dialogTitle, message); + } else { + MessageDialog.openInformation(getShell(), dialogTitle, message); + } } else { - MessageDialog.openError(getShell(), "Create Volume", "Volume creation failed! [" + status.getCode() + "][" - + status.getMessage() + "]"); + if (status.isPartSuccess()) { + MessageDialog.openWarning(getShell(), dialogTitle, "Volume created, but following error(s) occured: " + + status); + } else { + MessageDialog.openError(getShell(), dialogTitle, "Volume creation failed! " + status); + } } return true; diff --git a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/dialogs/CreateVolumeDisksPage.java b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/dialogs/DisksSelectionPage.java index c78601d9..e50e81a7 100644 --- a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/dialogs/CreateVolumeDisksPage.java +++ b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/dialogs/DisksSelectionPage.java @@ -19,7 +19,6 @@ package com.gluster.storage.management.gui.dialogs; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import org.eclipse.jface.viewers.ITableLabelProvider; @@ -48,7 +47,7 @@ import com.richclientgui.toolbox.duallists.IRemovableContentProvider; import com.richclientgui.toolbox.duallists.RemovableContentProvider; import com.richclientgui.toolbox.duallists.TableColumnData; -public class CreateVolumeDisksPage extends Composite { +public class DisksSelectionPage extends Composite { private enum DISK_TABLE_COLUMN_INDICES { SERVER, DISK, SPACE, SPACE_USED } @@ -67,13 +66,17 @@ public class CreateVolumeDisksPage extends Composite { private Button btnDown; - public CreateVolumeDisksPage(final Composite parent, int style, List<Disk> allDisks, List<Disk> selectedDisks) { + public DisksSelectionPage(final Composite parent, int style, List<Disk> allDisks, List<Disk> selectedDisks) { super(parent, style); createPage(allDisks, selectedDisks); parent.layout(); } + + public void addDiskSelectionListener(ListContentChangedListener<Disk> listener) { + dualTableViewer.addChosenListChangedSelectionListener(listener); + } private TableLabelProviderAdapter getDiskLabelProvider() { return new TableLabelProviderAdapter() { diff --git a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/dialogs/SelectDisksDialog.java b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/dialogs/SelectDisksDialog.java index 0ec19d5b..08a35357 100644 --- a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/dialogs/SelectDisksDialog.java +++ b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/dialogs/SelectDisksDialog.java @@ -24,7 +24,6 @@ import java.util.List; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.osgi.framework.internal.core.Msg; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.GridData; @@ -33,13 +32,11 @@ import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Shell; -import com.gluster.storage.management.client.GlusterDataModelManager; import com.gluster.storage.management.core.model.Disk; -import com.gluster.storage.management.core.model.Volume; public class SelectDisksDialog extends Dialog { - private CreateVolumeDisksPage disksPage; + private DisksSelectionPage disksPage; private List<Disk> allDisks; private List<Disk> selectedDisks; @@ -58,9 +55,9 @@ public class SelectDisksDialog extends Dialog { private List<Disk> getSelectedDisks(List<Disk> allDisks, List<String> selectedDisks) { List<Disk> disks = new ArrayList<Disk>(); for (String selectedDisk : selectedDisks) { - for (Disk disk : disks) { + for (Disk disk : allDisks) { String brick[] = selectedDisk.split(":"); - if (disk.getServerName() == brick[0] && disk.getName() == brick[1]) { + if (disk.getServerName().equals(brick[0]) && disk.getName().equals(brick[1])) { disks.add(disk); } } @@ -78,14 +75,12 @@ public class SelectDisksDialog extends Dialog { Composite container = new Composite(parent, SWT.NONE); GridLayout containerLayout = new GridLayout(2, false); container.setLayout(containerLayout); - GridData containerLayoutData = new GridData(SWT.FILL, SWT.FILL, true, - true); + GridData containerLayoutData = new GridData(SWT.FILL, SWT.FILL, true, true); container.setLayoutData(containerLayoutData); getShell().setText("Create Volume - Select Disks"); - disksPage = new CreateVolumeDisksPage(container, SWT.NONE, allDisks, - selectedDisks); + disksPage = new DisksSelectionPage(container, SWT.NONE, allDisks, selectedDisks); return container; } @@ -97,10 +92,8 @@ public class SelectDisksDialog extends Dialog { */ @Override protected void createButtonsForButtonBar(Composite parent) { - createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, - true); - createButton(parent, IDialogConstants.CANCEL_ID, - IDialogConstants.CANCEL_LABEL, false); + createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true); + createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false); } /** @@ -112,15 +105,14 @@ public class SelectDisksDialog extends Dialog { } @Override - protected void cancelPressed() { + protected void cancelPressed() { System.out.println("Test"); super.cancelPressed(); } @Override protected void okPressed() { if (this.getSelectedDisks().size() == 0) { - MessageDialog.openError(getShell(), "Select Disk(s)", - "Please select atlease one disk"); + MessageDialog.openError(getShell(), "Select Disk(s)", "Please select atlease one disk"); } else { super.okPressed(); } @@ -129,7 +121,7 @@ public class SelectDisksDialog extends Dialog { public List<Disk> getSelectedDisks() { return disksPage.getChosenDisks(); } - + public List<String> getSelectedBricks() { return disksPage.getChosenBricks(); } diff --git a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/login/LoginDialog.java b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/login/LoginDialog.java index 30406e27..c6ffa8d5 100644 --- a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/login/LoginDialog.java +++ b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/login/LoginDialog.java @@ -41,8 +41,6 @@ import org.eclipse.swt.widgets.Text; import com.gluster.storage.management.client.GlusterDataModelManager; import com.gluster.storage.management.client.UsersClient; -import com.gluster.storage.management.client.constants.ClientConstants; -import com.gluster.storage.management.core.exceptions.GlusterRuntimeException; import com.gluster.storage.management.core.model.ConnectionDetails; import com.gluster.storage.management.gui.IImageKeys; import com.gluster.storage.management.gui.utils.GUIHelper; @@ -197,12 +195,11 @@ public class LoginDialog extends Dialog { try { GlusterDataModelManager.getInstance().initializeModel(usersClient.getSecurityToken()); super.okPressed(); - } catch (GlusterRuntimeException e) { + } catch (Exception e) { setReturnCode(RETURN_CODE_ERROR); MessageDialog.openError(getShell(), "Initialization Error", e.getMessage()); close(); } - } else { MessageDialog.openError(getShell(), "Authentication Failed", "Invalid User ID or password"); } diff --git a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/toolbar/GlusterToolbarManager.java b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/toolbar/GlusterToolbarManager.java index a98c7862..cef0bf5d 100644 --- a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/toolbar/GlusterToolbarManager.java +++ b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/toolbar/GlusterToolbarManager.java @@ -30,7 +30,7 @@ import com.gluster.storage.management.core.model.EntityGroup; import com.gluster.storage.management.core.model.GlusterServer; import com.gluster.storage.management.core.model.Server; import com.gluster.storage.management.core.model.Volume; -import com.gluster.storage.management.gui.actions.IActionSetIDs; +import com.gluster.storage.management.gui.actions.IActionConstants; public class GlusterToolbarManager implements ToolbarManager { private enum ENTITY_TYPE { @@ -53,13 +53,13 @@ public class GlusterToolbarManager implements ToolbarManager { private Map<ENTITY_TYPE, String> createActionSetMap() { Map<ENTITY_TYPE, String> actionSetMap = new HashMap<GlusterToolbarManager.ENTITY_TYPE, String>(); - actionSetMap.put(ENTITY_TYPE.CLUSTER, IActionSetIDs.ACTION_SET_CLUSTER); - actionSetMap.put(ENTITY_TYPE.VOLUMES, IActionSetIDs.ACTION_SET_VOLUMES); - actionSetMap.put(ENTITY_TYPE.VOLUME, IActionSetIDs.ACTION_SET_VOLUME); - actionSetMap.put(ENTITY_TYPE.GLUSTER_SERVERS, IActionSetIDs.ACTION_SET_GLUSTER_SERVERS); - actionSetMap.put(ENTITY_TYPE.GLUSTER_SERVER, IActionSetIDs.ACTION_SET_GLUSTER_SERVER); - actionSetMap.put(ENTITY_TYPE.DISCOVERED_SERVERS, IActionSetIDs.ACTION_SET_DISCOVERED_SERVERS); - actionSetMap.put(ENTITY_TYPE.DISCOVERED_SERVER, IActionSetIDs.ACTION_SET_DISCOVERED_SERVER); + actionSetMap.put(ENTITY_TYPE.CLUSTER, IActionConstants.ACTION_SET_CLUSTER); + actionSetMap.put(ENTITY_TYPE.VOLUMES, IActionConstants.ACTION_SET_VOLUMES); + actionSetMap.put(ENTITY_TYPE.VOLUME, IActionConstants.ACTION_SET_VOLUME); + actionSetMap.put(ENTITY_TYPE.GLUSTER_SERVERS, IActionConstants.ACTION_SET_GLUSTER_SERVERS); + actionSetMap.put(ENTITY_TYPE.GLUSTER_SERVER, IActionConstants.ACTION_SET_GLUSTER_SERVER); + actionSetMap.put(ENTITY_TYPE.DISCOVERED_SERVERS, IActionConstants.ACTION_SET_DISCOVERED_SERVERS); + actionSetMap.put(ENTITY_TYPE.DISCOVERED_SERVER, IActionConstants.ACTION_SET_DISCOVERED_SERVER); return actionSetMap; } diff --git a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/utils/GUIHelper.java b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/utils/GUIHelper.java index 9f5fdfb7..89c5a78e 100644 --- a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/utils/GUIHelper.java +++ b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/utils/GUIHelper.java @@ -18,7 +18,9 @@ *******************************************************************************/ package com.gluster.storage.management.gui.utils; +import java.util.ArrayList; import java.util.Iterator; +import java.util.List; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IAction; @@ -55,7 +57,9 @@ import org.eclipse.swt.widgets.TableColumn; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.IViewPart; import org.eclipse.ui.IViewReference; +import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.IWorkbenchSite; +import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.forms.events.ExpansionAdapter; @@ -67,6 +71,9 @@ import org.eclipse.ui.forms.widgets.Section; import org.eclipse.ui.progress.IProgressConstants; import com.gluster.storage.management.core.exceptions.GlusterRuntimeException; +import com.gluster.storage.management.core.model.Disk; +import com.gluster.storage.management.core.model.Entity; +import com.gluster.storage.management.gui.Application; import com.gluster.storage.management.gui.IImageKeys; import com.gluster.storage.management.gui.views.NavigationView; @@ -250,6 +257,10 @@ public class GUIHelper { } return null; } + + public IWorkbenchPart getActiveView() { + return PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActivePart(); + } public ControlDecoration createErrorDecoration(Control control) { ControlDecoration passwordErrorDecoration = new ControlDecoration(control, SWT.LEFT | SWT.TOP); @@ -348,7 +359,11 @@ public class GUIHelper { * @return The selected object of given type if found, else null */ public Object getSelectedEntity(IWorkbenchSite site, Class expectedType) { - ISelection selection = site.getWorkbenchWindow().getSelectionService().getSelection(NavigationView.ID); + return getSelectedEntity(site.getWorkbenchWindow(), expectedType); + } + + public Object getSelectedEntity(IWorkbenchWindow window, Class expectedType) { + ISelection selection = window.getSelectionService().getSelection(NavigationView.ID); if (selection instanceof IStructuredSelection) { Iterator<Object> iter = ((IStructuredSelection) selection).iterator(); while (iter.hasNext()) { @@ -360,6 +375,7 @@ public class GUIHelper { } return null; } + public void showProgressView() { try { @@ -370,4 +386,12 @@ public class GUIHelper { throw new GlusterRuntimeException("Could not open the progress view!", e); } } + + public void setStatusMessage(String message) { + Application.getApplication().getStatusLineManager().setMessage(message); + } + + public void clearStatusMessage() { + Application.getApplication().getStatusLineManager().setMessage(null); + } } diff --git a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/ClusterSummaryView.java b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/ClusterSummaryView.java index b2d2b93a..079d6bcf 100644 --- a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/ClusterSummaryView.java +++ b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/ClusterSummaryView.java @@ -25,18 +25,24 @@ import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.forms.events.HyperlinkAdapter; +import org.eclipse.ui.forms.events.HyperlinkEvent; import org.eclipse.ui.forms.widgets.FormToolkit; import org.eclipse.ui.forms.widgets.ImageHyperlink; import org.eclipse.ui.forms.widgets.ScrolledForm; +import org.eclipse.ui.handlers.IHandlerService; import org.eclipse.ui.part.ViewPart; import com.gluster.storage.management.client.GlusterDataModelManager; import com.gluster.storage.management.core.model.Cluster; +import com.gluster.storage.management.core.model.EntityGroup; +import com.gluster.storage.management.core.model.GlusterDataModel; import com.gluster.storage.management.core.model.GlusterServer; import com.gluster.storage.management.core.model.GlusterServer.SERVER_STATUS; +import com.gluster.storage.management.core.model.Server; import com.gluster.storage.management.core.model.Volume; import com.gluster.storage.management.core.model.Volume.VOLUME_STATUS; import com.gluster.storage.management.gui.IImageKeys; +import com.gluster.storage.management.gui.actions.IActionConstants; import com.gluster.storage.management.gui.utils.GUIHelper; import com.gluster.storage.management.gui.views.details.tabcreators.PieChartViewerComposite; @@ -50,6 +56,7 @@ public class ClusterSummaryView extends ViewPart { private final FormToolkit toolkit = new FormToolkit(Display.getCurrent()); private ScrolledForm form; private Cluster cluster; + private GlusterDataModel model = GlusterDataModelManager.getInstance().getModel(); /* * (non-Javadoc) @@ -59,10 +66,9 @@ public class ClusterSummaryView extends ViewPart { @Override public void createPartControl(Composite parent) { if (cluster == null) { - //cluster = (Cluster)guiHelper.getSelectedEntity(getSite(), Cluster.class); - cluster = (Cluster)GlusterDataModelManager.getInstance().getModel().getChildren().get(0); + cluster = model.getCluster(); } - + createSections(parent); } @@ -75,7 +81,7 @@ public class ClusterSummaryView extends ViewPart { } return count; } - + private int getServerCountByStatus(Cluster cluster, SERVER_STATUS status) { int count = 0; for (GlusterServer server : cluster.getServers()) { @@ -105,12 +111,13 @@ public class ClusterSummaryView extends ViewPart { private void createStatusChart(FormToolkit toolkit, Composite section, Double[] values) { String[] categories = new String[] { "Online", "Offline" }; - PieChartViewerComposite chartViewerComposite = new PieChartViewerComposite(section, SWT.NONE, categories, values); + PieChartViewerComposite chartViewerComposite = new PieChartViewerComposite(section, SWT.NONE, categories, + values); GridData data = new GridData(SWT.FILL, SWT.FILL, true, true); data.widthHint = 250; data.heightHint = 250; - chartViewerComposite.setLayoutData(data); + chartViewerComposite.setLayoutData(data); } private void createActionsSection() { @@ -120,14 +127,30 @@ public class ClusterSummaryView extends ViewPart { imageHyperlink.setText("Create Volume"); imageHyperlink.setImage(guiHelper.getImage(IImageKeys.CREATE_VOLUME_BIG)); imageHyperlink.addHyperlinkListener(new HyperlinkAdapter() { - // TODO: Override appropriate method and handle hyperlink event + @Override + public void linkActivated(HyperlinkEvent e) { + IHandlerService hs = (IHandlerService) getSite().getService(IHandlerService.class); + try { + hs.executeCommand(IActionConstants.COMMAND_CREATE_VOLUME, null); + } catch (Exception e1) { + e1.printStackTrace(); + } + } }); imageHyperlink = toolkit.createImageHyperlink(section, SWT.NONE); imageHyperlink.setText("Add Server(s)"); imageHyperlink.setImage(guiHelper.getImage(IImageKeys.ADD_SERVER_BIG)); imageHyperlink.addHyperlinkListener(new HyperlinkAdapter() { - // TODO: Override appropriate method and handle hyperlink event + @Override + public void linkActivated(HyperlinkEvent e) { + // Open the "discovered servers" view by selecting the corresponding entity in the navigation view + EntityGroup<Server> autoDiscoveredServersEntityGroup = GlusterDataModelManager.getInstance().getModel() + .getCluster().getAutoDiscoveredServersEntityGroup(); + + NavigationView navigationView = (NavigationView) guiHelper.getView(NavigationView.ID); + navigationView.selectEntity(autoDiscoveredServersEntityGroup); + } }); } diff --git a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/NavigationView.java b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/NavigationView.java index 4de5b61a..aede70c5 100644 --- a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/NavigationView.java +++ b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/NavigationView.java @@ -90,13 +90,7 @@ public class NavigationView extends ViewPart implements ISelectionListener { @Override public void volumeChanged(Volume volume, Event event) { super.volumeChanged(volume, event); - selectEntity(volume); // this makes sure that the toolbar buttons get updated accoring to new status - } - - @Override - public void volumeCreated(Volume volume) { - super.volumeCreated(volume); - selectEntity(volume); + selectEntity(volume); // this makes sure that the toolbar buttons get updated according to new status } }); } diff --git a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/VolumeSummaryView.java b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/VolumeSummaryView.java index 798c2a40..1c9577ac 100644 --- a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/VolumeSummaryView.java +++ b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/VolumeSummaryView.java @@ -1,7 +1,17 @@ package com.gluster.storage.management.gui.views; +import java.util.List; +import java.util.Map.Entry; + +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.fieldassist.ControlDecoration; import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.BusyIndicator; import org.eclipse.swt.custom.CLabel; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Button; @@ -17,13 +27,17 @@ import org.eclipse.ui.forms.widgets.ScrolledForm; import org.eclipse.ui.part.ViewPart; import com.gluster.storage.management.client.GlusterDataModelManager; +import com.gluster.storage.management.client.VolumesClient; +import com.gluster.storage.management.core.model.Alert; import com.gluster.storage.management.core.model.DefaultClusterListener; import com.gluster.storage.management.core.model.Event; import com.gluster.storage.management.core.model.Event.EVENT_TYPE; +import com.gluster.storage.management.core.model.Status; import com.gluster.storage.management.core.model.Volume; import com.gluster.storage.management.core.model.Volume.NAS_PROTOCOL; import com.gluster.storage.management.core.model.Volume.VOLUME_TYPE; import com.gluster.storage.management.core.utils.NumberUtil; +import com.gluster.storage.management.core.utils.ValidationUtil; import com.gluster.storage.management.gui.IImageKeys; import com.gluster.storage.management.gui.toolbar.GlusterToolbarManager; import com.gluster.storage.management.gui.utils.GUIHelper; @@ -31,11 +45,17 @@ import com.gluster.storage.management.gui.utils.GUIHelper; public class VolumeSummaryView extends ViewPart { public static final String ID = VolumeSummaryView.class.getName(); private static final GUIHelper guiHelper = GUIHelper.getInstance(); + private final FormToolkit toolkit = new FormToolkit(Display.getCurrent()); private ScrolledForm form; private Volume volume; private CLabel lblStatusValue; private DefaultClusterListener volumeChangedListener; + private Hyperlink changeLink; + private Text accessControlText; + private ControlDecoration errDecoration; + private Composite parent; + private static final String COURIER_FONT = "Courier"; @Override public void createPartControl(Composite parent) { @@ -43,22 +63,35 @@ public class VolumeSummaryView extends ViewPart { volume = (Volume) guiHelper.getSelectedEntity(getSite(), Volume.class); } - createSections(parent); - + this.parent = parent; + createSections(); + // Refresh the navigation tree whenever there is a change to the data model volumeChangedListener = new DefaultClusterListener() { + @SuppressWarnings("unchecked") @Override public void volumeChanged(Volume volume, Event event) { - if(event.getEventType() == EVENT_TYPE.VOLUME_STATUS_CHANGED) { + if (event.getEventType() == EVENT_TYPE.VOLUME_STATUS_CHANGED) { updateVolumeStatusLabel(); new GlusterToolbarManager(getSite().getWorkbenchWindow()).updateToolbar(volume); + } else if (event.getEventType() == EVENT_TYPE.VOLUME_OPTION_SET) { + Entry<String, String> option = (Entry<String, String>) event.getEventData(); + if (option.getKey().equals(Volume.OPTION_AUTH_ALLOW)) { + // access control option value has changed. update the text field with new value. + populateAccessControlText(); + } + } else if (event.getEventType() == EVENT_TYPE.VOLUME_OPTIONS_RESET) { + // all volume options reset. populate access control text with default value. + populateAccessControlText(); } } }; GlusterDataModelManager.getInstance().addClusterListener(volumeChangedListener); } - - /* (non-Javadoc) + + /* + * (non-Javadoc) + * * @see org.eclipse.ui.part.WorkbenchPart#dispose() */ @Override @@ -67,7 +100,7 @@ public class VolumeSummaryView extends ViewPart { GlusterDataModelManager.getInstance().removeClusterListener(volumeChangedListener); } - private void createSections(Composite parent) { + private void createSections() { form = guiHelper.setupForm(parent, toolkit, "Volume Properties [" + volume.getName() + "]"); createVolumePropertiesSection(); @@ -78,13 +111,69 @@ public class VolumeSummaryView extends ViewPart { } private void createVolumeAlertsSection() { - Composite section = guiHelper.createSection(form, toolkit, "Alerts", null, 3, false); - toolkit.createLabel(section, "Volume related alerts will be displayed here"); + Composite section = guiHelper.createSection(form, toolkit, "Alerts", null, 1, false); + List<Alert> alerts = GlusterDataModelManager.getInstance().getModel().getCluster().getAlerts(); + + for (int i = 0; i < alerts.size(); i++) { + if (alerts.get(i).getType() == Alert.ALERT_TYPES.OFFLINE_VOLUME_DISKS_ALERT + && alerts.get(i).getReference().split(":")[0].trim().equals(volume.getName())) { + addAlertLabel(section, alerts.get(i)); + } + } + } + + private void addAlertLabel(Composite section, Alert alert) { + CLabel lblAlert = new CLabel(section, SWT.NONE); + lblAlert.setImage(guiHelper.getImage(IImageKeys.DISK_OFFLINE)); + lblAlert.setText(alert.getMessage()); + lblAlert.redraw(); + } + + private Label setLabelStyle(Label label, String fontName, int size, int style) { + Font font = new Font(Display.getCurrent(), new FontData(fontName, size, style)); + label.setFont(font); + return label; } private void createVolumeMountingInfoSection() { - Composite section = guiHelper.createSection(form, toolkit, "Mounting Information", null, 3, false); - toolkit.createLabel(section, "Information about mounting the\nvolume will be printed here"); + String glusterFs = "Gluster:"; + String nfs = "NFS:"; + String glusterFsSyntax = "mount -t glusterfs <SERVER-NAME>:/<VOLUME-NAME> <MOUNT-POINT>"; + String nfsSyntax = "mount -t nfs <SERVER-NAME>:/nfs/<VOLUME-NAME> <MOUNT-POINT>"; + String info = "<SERVER-NAME> - Any server name in the storage cloud"; + String volumeName = volume.getName().trim(); + String serverName = volume.getDisks().get(0).split(":")[0].trim(); // disk if the form of: "server:disk" + + Composite section = guiHelper.createSection(form, toolkit, "Mounting Information", null, 2, false); + + Label lbl = toolkit.createLabel(section, "Syntax"); + final int defaultFontSize = lbl.getFont().getFontData()[0].getHeight(); + final String defaultFontName = lbl.getFont().getFontData()[0].name; + + setLabelStyle(lbl, defaultFontName, defaultFontSize, SWT.BOLD); + toolkit.createLabel(section, ""); + + setLabelStyle(toolkit.createLabel(section, glusterFs), defaultFontName, defaultFontSize, SWT.NORMAL); + setLabelStyle(toolkit.createLabel(section, glusterFsSyntax, SWT.NONE), COURIER_FONT, 10, SWT.NONE); + + // TODO: Check required if nfs is optional + setLabelStyle(toolkit.createLabel(section, nfs), defaultFontName, defaultFontSize, SWT.NORMAL); + setLabelStyle(toolkit.createLabel(section, nfsSyntax, SWT.NONE), COURIER_FONT, 10, SWT.NONE); + + toolkit.createLabel(section, ""); + setLabelStyle(toolkit.createLabel(section, info), defaultFontName, (defaultFontSize - 1), SWT.NONE); + + setLabelStyle(toolkit.createLabel(section, "Example"), defaultFontName, defaultFontSize, SWT.BOLD); + toolkit.createLabel(section, ""); + + setLabelStyle(toolkit.createLabel(section, glusterFs), defaultFontName, defaultFontSize, SWT.NORMAL); + setLabelStyle(toolkit.createLabel(section, "#mount -t glusterfs " + serverName + ":/" + volumeName + " /mnt"), + COURIER_FONT, 10, SWT.NONE); + + // TODO: Check required if nfs is optional + setLabelStyle(toolkit.createLabel(section, nfs), defaultFontName, defaultFontSize, SWT.NORMAL); + setLabelStyle(toolkit.createLabel(section, "#mount -t nfs " + serverName + ":/" + volumeName + " /mnt"), + COURIER_FONT, 10, SWT.NONE); } /** @@ -114,33 +203,47 @@ public class VolumeSummaryView extends ViewPart { private GridData createDefaultLayoutData() { GridData layoutData = new GridData(); - layoutData.minimumWidth = 150; - layoutData.widthHint = 150; + layoutData.minimumWidth = 300; + layoutData.widthHint = 300; return layoutData; } private void createAccessControlField(Composite section) { toolkit.createLabel(section, "Access Control: ", SWT.NONE); - Text accessControlText = toolkit.createText(section, volume.getAccessControlList()); + accessControlText = toolkit.createText(section, volume.getAccessControlList()); + + populateAccessControlText(); + addKeyListerForAccessControl(); accessControlText.setLayoutData(createDefaultLayoutData()); accessControlText.setEnabled(false); - createChangeLinkForAccessControl(section, accessControlText); + createChangeLinkForAccessControl(section); + + // error decoration used while validating the access control text + errDecoration = guiHelper.createErrorDecoration(accessControlText); + errDecoration.hide(); + createAccessControlInfoLabel(section); // info text + } + + private void createAccessControlInfoLabel(Composite section) { + toolkit.createLabel(section, "", SWT.NONE); + Label accessControlInfoLabel = toolkit.createLabel(section, "(Comma separated list of IP addresses/hostnames)"); + GridData data = new GridData(SWT.LEFT, SWT.CENTER, true, false); + data.horizontalSpan = 2; + accessControlInfoLabel.setLayoutData(data); } - private void createChangeLinkForAccessControl(Composite section, final Text accessControlText) { - final Hyperlink changeLink = toolkit.createHyperlink(section, "change", SWT.NONE); + private void createChangeLinkForAccessControl(Composite section) { + changeLink = toolkit.createHyperlink(section, "change", SWT.NONE); changeLink.addHyperlinkListener(new HyperlinkAdapter() { private void finishEdit() { - // TODO: Update value to back-end - // TODO: Validation of entered text - volume.setAccessControlList(accessControlText.getText()); - accessControlText.setEnabled(false); - changeLink.setText("change"); + saveAccessControlList(); } private void startEdit() { accessControlText.setEnabled(true); + accessControlText.setFocus(); + accessControlText.selectAll(); changeLink.setText("update"); } @@ -157,6 +260,71 @@ public class VolumeSummaryView extends ViewPart { }); } + private void saveAccessControlList() { + final String newACL = accessControlText.getText(); + + guiHelper.setStatusMessage("Setting access control list to [" + newACL + "]..."); + parent.update(); + + if (newACL.equals(volume.getAccessControlList())) { + accessControlText.setEnabled(false); + changeLink.setText("change"); + } else if (ValidationUtil.isValidAccessControl(newACL)) { + BusyIndicator.showWhile(Display.getDefault(), new Runnable() { + @Override + public void run() { + Status status = (new VolumesClient(GlusterDataModelManager.getInstance().getSecurityToken())) + .setVolumeOption(volume.getName(), Volume.OPTION_AUTH_ALLOW, newACL); + + if (status.isSuccess()) { + accessControlText.setEnabled(false); + changeLink.setText("change"); + + GlusterDataModelManager.getInstance().setAccessControlList(volume, newACL); + } else { + MessageDialog.openError(Display.getDefault().getActiveShell(), "Access control", + status.getMessage()); + } + } + }); + } else { + MessageDialog.openError(Display.getDefault().getActiveShell(), "Access control", "Invalid IP / Host name "); + } + guiHelper.clearStatusMessage(); + parent.update(); + } + + private void addKeyListerForAccessControl() { + accessControlText.addKeyListener(new KeyAdapter() { + public void keyReleased(KeyEvent key) { + switch (key.keyCode) { + case SWT.ESC: + // Reset to default + populateAccessControlText(); + changeLink.setText("change"); + accessControlText.setEnabled(false); + break; + case 13: + // User has pressed enter. Save the new value + saveAccessControlList(); + break; + } + + validateAccessControlList(); + } + }); + } + + private void populateAccessControlText() { + String accessControlList = volume.getAccessControlList(); + if (accessControlList == null) { + // if not set, show default value + accessControlList = GlusterDataModelManager.getInstance().getVolumeOptionDefaultValue( + Volume.OPTION_AUTH_ALLOW); + } + accessControlText.setText(accessControlList); + } + private void createNASProtocolField(Composite section) { toolkit.createLabel(section, "NAS Protocols: ", SWT.NONE); @@ -167,7 +335,8 @@ public class VolumeSummaryView extends ViewPart { final Button nfsCheckBox = createCheckbox(nasProtocolsComposite, "NFS", volume.getNASProtocols().contains(NAS_PROTOCOL.NFS)); - createChangeLinkForNASProtocol(section, nfsCheckBox); + toolkit.createLabel(section, "", SWT.NONE); // dummy + // createChangeLinkForNASProtocol(section, nfsCheckBox); } private Button createCheckbox(Composite parent, String label, boolean selected) { @@ -178,8 +347,8 @@ public class VolumeSummaryView extends ViewPart { } private void createChangeLinkForNASProtocol(Composite section, final Button nfsCheckBox) { - final Hyperlink changeLink = toolkit.createHyperlink(section, "change", SWT.NONE); - changeLink.addHyperlinkListener(new HyperlinkAdapter() { + final Hyperlink nasChangeLink = toolkit.createHyperlink(section, "change", SWT.NONE); + nasChangeLink.addHyperlinkListener(new HyperlinkAdapter() { private void finishEdit() { // TODO: Update value to back-end @@ -189,12 +358,12 @@ public class VolumeSummaryView extends ViewPart { volume.disableNFS(); } nfsCheckBox.setEnabled(false); - changeLink.setText("change"); + nasChangeLink.setText("change"); } private void startEdit() { nfsCheckBox.setEnabled(true); - changeLink.setText("update"); + nasChangeLink.setText("update"); } @Override @@ -267,4 +436,20 @@ public class VolumeSummaryView extends ViewPart { public void setFocus() { form.setFocus(); } + + private void validateAccessControlList() { + errDecoration.hide(); + + if (accessControlText.getText().length() == 0) { + errDecoration.setDescriptionText("Access control list cannot be empty!"); + errDecoration.show(); + return; + } + + if (!ValidationUtil.isValidAccessControl(accessControlText.getText())) { + errDecoration + .setDescriptionText("Access control list must be a comma separated list of IP addresses/Host names. Please enter a valid value!"); + errDecoration.show(); + } + } } diff --git a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/VolumesSummaryView.java b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/VolumesSummaryView.java index 8c39fbeb..b929a2b2 100644 --- a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/VolumesSummaryView.java +++ b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/VolumesSummaryView.java @@ -33,7 +33,6 @@ import org.eclipse.ui.part.ViewPart; import com.gluster.storage.management.core.model.Alert; import com.gluster.storage.management.core.model.EntityGroup; -import com.gluster.storage.management.core.model.Cluster; import com.gluster.storage.management.client.GlusterDataModelManager; import com.gluster.storage.management.core.model.RunningTask; import com.gluster.storage.management.core.model.Volume; @@ -96,12 +95,11 @@ public class VolumesSummaryView extends ViewPart { } private void addAlertLabel(Composite section, Alert alert) { - if (alert.getType() == Alert.ALERT_TYPES.DISK_USAGE_ALERT - || alert.getType() == Alert.ALERT_TYPES.OFFLINE_VOLUME_DISKS_ALERT) { + if (alert.getType() == Alert.ALERT_TYPES.OFFLINE_VOLUME_DISKS_ALERT) { CLabel lblAlert = new CLabel(section, SWT.NONE); - lblAlert.setText(alert.getMessage()); lblAlert.setImage((alert.getType() == Alert.ALERT_TYPES.DISK_USAGE_ALERT) ? guiHelper .getImage(IImageKeys.LOW_DISK_SPACE) : guiHelper.getImage(IImageKeys.DISK_OFFLINE)); + lblAlert.setText(alert.getMessage()); lblAlert.redraw(); } } diff --git a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/details/AbstractDisksPage.java b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/details/AbstractDisksPage.java index 593f7ba1..6b516019 100644 --- a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/details/AbstractDisksPage.java +++ b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/details/AbstractDisksPage.java @@ -94,6 +94,7 @@ public abstract class AbstractDisksPage extends Composite implements IEntityList setupPageLayout(); Text filterText = guiHelper.createFilterText(toolkit, this); setupDiskTableViewer(createTableViewerComposite(), filterText); + site.setSelectionProvider(tableViewer); tableViewer.setInput(disks); setupStatusCellEditor(); // creates hyperlinks for "unitialized" disks @@ -231,7 +232,7 @@ public abstract class AbstractDisksPage extends Composite implements IEntityList } private TableViewer createDiskTableViewer(Composite parent) { - tableViewer = CheckboxTableViewer.newCheckList(parent, SWT.FLAT | SWT.FULL_SELECTION | SWT.MULTI); + tableViewer = CheckboxTableViewer.newCheckList(parent, SWT.FLAT | SWT.FULL_SELECTION | SWT.MULTI ); tableViewer.setLabelProvider(getTableLabelProvider()); tableViewer.setContentProvider(new ArrayContentProvider()); diff --git a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/details/OptionKeyEditingSupport.java b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/details/OptionKeyEditingSupport.java new file mode 100644 index 00000000..27dc8d4b --- /dev/null +++ b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/details/OptionKeyEditingSupport.java @@ -0,0 +1,125 @@ +/** + * + */ +package com.gluster.storage.management.gui.views.details; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.eclipse.jface.viewers.CellEditor; +import org.eclipse.jface.viewers.ColumnViewer; +import org.eclipse.jface.viewers.ComboBoxCellEditor; +import org.eclipse.jface.viewers.EditingSupport; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; + +import com.gluster.storage.management.client.GlusterDataModelManager; +import com.gluster.storage.management.core.model.Volume; +import com.gluster.storage.management.core.model.VolumeOptionInfo; + +/** + * Editing support for the "value" column in volume options table viewer. + */ +public class OptionKeyEditingSupport extends EditingSupport { + private CellEditor cellEditor; + private Volume volume; + private List<VolumeOptionInfo> defaults = GlusterDataModelManager.getInstance().getVolumeOptionsDefaults(); + private String[] allowedKeys; + private ColumnViewer viewer; + + public OptionKeyEditingSupport(ColumnViewer viewer, Volume volume) { + super(viewer); + this.volume = volume; + this.viewer = viewer; + } + + /** + * @return array of option keys that are not already set on the volume + */ + private String[] getAllowedKeys() { + ArrayList<String> keys = new ArrayList<String>(); + Map<String, String> volumeOptions = volume.getOptions(); + for(VolumeOptionInfo optionInfo : defaults) { + String optionName = optionInfo.getName(); + if(!volumeOptions.containsKey(optionName) || volumeOptions.get(optionName).isEmpty()) { + // key not set => available for setting + // value not set => this is the row being edited + keys.add(optionName); + } + } + return keys.toArray(new String[0]); + } + + @SuppressWarnings("unchecked") + @Override + protected void setValue(final Object element, final Object value) { + Entry<String, String> oldEntry = (Entry<String, String>)element; + Integer newValue = (Integer)value; + String newKey = allowedKeys[newValue]; + + if (((Entry<String, String>)element).getKey().equals(newKey)) { + // selected value is same as old one. + return; + } + + // value has changed. set new value and refresh the viewer. + volume.getOptions().remove(oldEntry.getKey()); + volume.setOption(newKey, ""); + getViewer().refresh(); + } + + @Override + protected Object getValue(Object element) { + Entry<String, String> entryBeingAdded = getEntryBeingAdded(); + if(entryBeingAdded == null) { + return cellEditor.getValue(); + } + + if(entryBeingAdded.getKey().isEmpty()) { + // editing just about to start. set first element as default. + return 0; + } + + return getIndexOfEntry(entryBeingAdded); + } + + @Override + protected CellEditor getCellEditor(Object element) { + allowedKeys = getAllowedKeys(); + cellEditor = new ComboBoxCellEditor((Composite) viewer.getControl(), allowedKeys, SWT.READ_ONLY); + return cellEditor; + } + + private int getIndexOfEntry(Entry<String, String> entryBeingAdded) { + for(int index = 0; index < allowedKeys.length; index++) { + if(allowedKeys[index].equals(entryBeingAdded.getKey())) { + return index; + } + } + return -1; + } + + protected Entry<String, String> getEntryBeingAdded() { + Entry<String, String> entryBeingAdded = null; + Iterator<Entry<String, String>> iter = volume.getOptions().entrySet().iterator(); + while(iter.hasNext()) { + Entry<String, String> nextEntry = iter.next(); + if(!iter.hasNext() && nextEntry.getValue().isEmpty()) { + // it's the LAST entry, and it's value is empty. + // means this is a new row being added in the table viewer. + entryBeingAdded = nextEntry; + } + } + return entryBeingAdded; + } + + @SuppressWarnings("unchecked") + @Override + protected boolean canEdit(Object element) { + Entry<String, String> entry = (Entry<String, String>)element; + return (entry.getKey().isEmpty() || entry.getValue().isEmpty()); + } +} diff --git a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/details/OptionValueEditingSupport.java b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/details/OptionValueEditingSupport.java new file mode 100644 index 00000000..af1ef949 --- /dev/null +++ b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/details/OptionValueEditingSupport.java @@ -0,0 +1,115 @@ +/** + * + */ +package com.gluster.storage.management.gui.views.details; + +import java.util.List; +import java.util.Map.Entry; + +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.viewers.CellEditor; +import org.eclipse.jface.viewers.ColumnViewer; +import org.eclipse.jface.viewers.EditingSupport; +import org.eclipse.jface.viewers.TextCellEditor; +import org.eclipse.swt.custom.BusyIndicator; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; + +import com.gluster.storage.management.client.GlusterDataModelManager; +import com.gluster.storage.management.client.VolumesClient; +import com.gluster.storage.management.core.model.Status; +import com.gluster.storage.management.core.model.Volume; +import com.gluster.storage.management.core.model.VolumeOptionInfo; +import com.gluster.storage.management.gui.utils.GUIHelper; + +/** + * Editing support for the "value" column in volume options table viewer. + */ +public class OptionValueEditingSupport extends EditingSupport { + private CellEditor cellEditor; + private Volume volume; + private List<VolumeOptionInfo> defaults = GlusterDataModelManager.getInstance().getVolumeOptionsDefaults(); + private GUIHelper guiHelper = GUIHelper.getInstance(); + + public OptionValueEditingSupport(ColumnViewer viewer, Volume volume) { + super(viewer); + this.volume = volume; + this.cellEditor = new TextCellEditor((Composite) viewer.getControl()); + } + + @SuppressWarnings("unchecked") + @Override + protected void setValue(final Object element, final Object value) { + final Entry<String, String> entry = (Entry<String, String>) element; + final String optionKey = entry.getKey(); + final String optionValue = (String)value; + final String oldValue = entry.getValue(); + + // It is not allowed to change value to empty string + if(optionValue.isEmpty()) { + MessageDialog.openError(Display.getDefault().getActiveShell(), "Set Volume Option", + "Option value can't be empty! Please enter a valid value."); + cellEditor.setFocus(); + return; + } + + if (oldValue.equals(optionValue)) { + // value is same as that present in the model. return without doing anything. + return; + } + + // value has changed. set volume option at back-end and update model accordingly + guiHelper.setStatusMessage("Setting option [" + optionKey + " = " + optionValue + "]..."); + getViewer().getControl().update(); + + BusyIndicator.showWhile(Display.getDefault(), new Runnable() { + + @Override + public void run() { + VolumesClient client = new VolumesClient(GlusterDataModelManager.getInstance().getSecurityToken()); + Status status = client.setVolumeOption(volume.getName(), entry.getKey(), (String) value); + if (status.isSuccess()) { + entry.setValue((String)value); + GlusterDataModelManager.getInstance().setVolumeOption(volume, entry); + } else { + MessageDialog.openError(Display.getDefault().getActiveShell(), "Set Volume Option", + status.getMessage()); + } + getViewer().update(entry, null); + } + }); + + guiHelper.clearStatusMessage(); + getViewer().getControl().update(); + } + + /** + * @param key Key whose default value is to be fetched + * @return Default value of the volume option with given key + */ + private String getDefaultValue(String key) { + for(VolumeOptionInfo optionInfo : defaults) { + if(optionInfo.getName().equals(key)) { + return optionInfo.getDefaultValue(); + } + } + return ""; + } + + @SuppressWarnings("unchecked") + @Override + protected Object getValue(Object element) { + Entry<String, String> entry = (Entry<String, String>) element; + return entry.getValue().isEmpty() ? getDefaultValue(entry.getKey()) : entry.getValue(); + } + + @Override + protected CellEditor getCellEditor(Object element) { + return cellEditor; + } + + @Override + protected boolean canEdit(Object element) { + return true; + } +} diff --git a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/details/VolumeLogsPage.java b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/details/VolumeLogsPage.java index d435201e..df9f8533 100644 --- a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/details/VolumeLogsPage.java +++ b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/details/VolumeLogsPage.java @@ -18,6 +18,11 @@ *******************************************************************************/ package com.gluster.storage.management.gui.views.details; +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.layout.TableColumnLayout; import org.eclipse.jface.viewers.ArrayContentProvider; import org.eclipse.jface.viewers.ColumnWeightData; @@ -25,6 +30,8 @@ import org.eclipse.jface.viewers.TableViewer; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; @@ -39,8 +46,15 @@ import org.eclipse.swt.widgets.TableColumn; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.forms.widgets.FormToolkit; -import com.gluster.storage.management.core.model.GlusterDummyModel; +import com.gluster.storage.management.client.GlusterDataModelManager; +import com.gluster.storage.management.client.VolumesClient; +import com.gluster.storage.management.core.constants.CoreConstants; +import com.gluster.storage.management.core.constants.GlusterConstants; +import com.gluster.storage.management.core.constants.GlusterConstants.VOLUME_LOG_LEVELS; +import com.gluster.storage.management.core.model.LogMessage; +import com.gluster.storage.management.core.model.Status; import com.gluster.storage.management.core.model.Volume; +import com.gluster.storage.management.core.response.LogMessageListResponse; import com.gluster.storage.management.gui.VolumeLogTableLabelProvider; import com.gluster.storage.management.gui.utils.GUIHelper; @@ -48,20 +62,35 @@ public class VolumeLogsPage extends Composite { private final FormToolkit toolkit = new FormToolkit(Display.getCurrent()); private final GUIHelper guiHelper = GUIHelper.getInstance(); - private Text text; + private Text filterText; + private Text lineCountText; + private Volume volume; + public enum LOG_TABLE_COLUMN_INDICES { DATE, TIME, DISK, SEVERITY, MESSAGE }; private static final String[] LOG_TABLE_COLUMN_NAMES = new String[] { "Date", "Time", "Disk", "Severity", "Message" }; + private TableViewer tableViewer; + private Combo disksCombo; + private Combo severityCombo; + private DateTime fromDate; + private DateTime fromTime; + private DateTime toDate; + private DateTime toTime; + private Button fromCheckbox; + private Button toCheckbox; /** - * Create the composite. + * Create the volume logs page * @param parent * @param style + * @param volume Volume for which the logs page is to be created */ public VolumeLogsPage(Composite parent, int style, Volume volume) { super(parent, style); + this.volume = volume; + addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { toolkit.dispose(); @@ -70,89 +99,257 @@ public class VolumeLogsPage extends Composite { toolkit.adapt(this); toolkit.paintBordersFor(this); - setLayout(new GridLayout(1, false)); - GridData layoutData = new GridData(); - layoutData.grabExcessHorizontalSpace = true; - layoutData.grabExcessVerticalSpace = true; - //layoutData.verticalIndent = 10; - setLayoutData(layoutData); + configureLayout(); Composite composite = toolkit.createComposite(this, SWT.NONE); toolkit.paintBordersFor(composite); - Label lblScanLast = toolkit.createLabel(composite, "Scan last", SWT.NONE); - lblScanLast.setBounds(0, 15, 80, 20); + createLineCountLabel(composite); + createLineCountText(composite); - text = toolkit.createText(composite, "100", SWT.NONE); - text.setBounds(85, 15, 60, 20); + createDiskLabel(composite); + createDisksCombo(composite); - Label lblMessagesAndFilter = toolkit.createLabel(composite, "messages, and filter on disk", SWT.NONE); - lblMessagesAndFilter.setBounds(160, 15, 200, 20); + createSeverityLabel(composite); + createSeverityCombo(composite); - Combo combo = new Combo(composite, SWT.NONE); - combo.setBounds(365, 15, 100, 20); - combo.setItems(new String[] {"ALL", "sda", "sdb", "sdc", "sdd"}); - toolkit.adapt(combo); - toolkit.paintBordersFor(combo); - combo.select(0); + createFromDateLabel(composite); + createFromDateField(composite); + createFromTimeField(composite); + createFromCheckbox(composite); - Label lblSeverity = toolkit.createLabel(composite, "Severity", SWT.NONE); - lblSeverity.setBounds(480, 15, 70, 20); + createToDateLabel(composite); + createToDateField(composite); + createToTimeField(composite); + createToCheckbox(composite); - Combo combo_1 = new Combo(composite, SWT.NONE); - combo_1.setBounds(555, 15, 110, 20); - combo_1.setItems(new String[] {"ALL", "SEVERE", "WARNING", "DEBUG", "INFO"}); - toolkit.adapt(combo_1); - toolkit.paintBordersFor(combo_1); - combo_1.select(1); + createSearchButton(composite); - Label lblFrom = toolkit.createLabel(composite, "from", SWT.NONE); - lblFrom.setBounds(0, 60, 40, 20); + createSeparator(composite); - DateTime dateTime = new DateTime(composite, SWT.BORDER | SWT.DROP_DOWN); - dateTime.setBounds(45, 60, 120, 20); - toolkit.adapt(dateTime); - toolkit.paintBordersFor(dateTime); + createFilterLabel(composite); + createFilterText(composite); - DateTime dateTime_1 = new DateTime(composite, SWT.BORDER | SWT.TIME); - dateTime_1.setBounds(171, 60, 120, 20); - toolkit.adapt(dateTime_1); - toolkit.paintBordersFor(dateTime_1); + createLogTableViewer(); + } + + private void createLogTableViewer() { + Composite tableViewerComposite = createTableViewerComposite(); - Label lblTo = toolkit.createLabel(composite, "To", SWT.NONE); - lblTo.setBounds(329, 60, 26, 20); + tableViewer = new TableViewer(tableViewerComposite, SWT.FLAT | SWT.FULL_SELECTION | SWT.MULTI); + tableViewer.setLabelProvider(new VolumeLogTableLabelProvider()); + tableViewer.setContentProvider(new ArrayContentProvider()); + + setupLogsTable(tableViewerComposite, tableViewer.getTable()); + guiHelper.createFilter(tableViewer, filterText, false); + } + + private void createFilterText(Composite composite) { + filterText = guiHelper.createFilterText(toolkit, composite); + filterText.setBounds(90, 105, 250, 20); + } + + private void createFilterLabel(Composite composite) { + Label lblFilterString = toolkit.createLabel(composite, "Filter String", SWT.LEFT); + lblFilterString.setBounds(0, 105, 85, 20); + } + + private void createSeparator(Composite composite) { + Label separator = toolkit.createLabel(composite, "", SWT.SEPARATOR | SWT.HORIZONTAL | SWT.FILL); + separator.setBounds(0, 95, 680, 2); + } + + private void createSearchButton(Composite composite) { + Button btnGo = toolkit.createButton(composite, "&Go", SWT.NONE); + btnGo.setBounds(615, 55, 50, 30); + btnGo.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + VolumesClient client = new VolumesClient(GlusterDataModelManager.getInstance().getSecurityToken()); + + Date fromTimestamp = null; + Date toTimestamp = null; + + if(fromCheckbox.getSelection()) { + fromTimestamp = extractTimestamp(fromDate, fromTime); + } + + if(toCheckbox.getSelection()) { + toTimestamp = extractTimestamp(toDate, toTime); + } + + if (!validateTimeRange(fromTimestamp, toTimestamp)) { + return; + } + + LogMessageListResponse response = client.getLogs(volume.getName(), disksCombo.getText(), + severityCombo.getText(), fromTimestamp, toTimestamp, Integer.parseInt(lineCountText.getText())); + Status status = response.getStatus(); + if(status.isSuccess()) { + List<LogMessage> logMessages = response.getLogMessages(); + tableViewer.setInput(logMessages.toArray(new LogMessage[0])); + tableViewer.refresh(); + } else { + MessageDialog.openError(getShell(), "Volume Logs", "Error while fetching volume logs: [" + status + + "]"); + } + } + }); + } + + protected boolean validateTimeRange(Date fromTimestamp, Date toTimestamp) { + if(fromTimestamp == null && toTimestamp == null) { + // no time range selected. nothing to validate. + return true; + } - DateTime dateTime_2 = new DateTime(composite, SWT.BORDER | SWT.DROP_DOWN); - dateTime_2.setBounds(355, 60, 120, 20); - toolkit.adapt(dateTime_2); - toolkit.paintBordersFor(dateTime_2); + Calendar calendar = Calendar.getInstance(); + Date now = calendar.getTime(); + if(fromTimestamp != null && fromTimestamp.after(now)) { + MessageDialog.openError(getShell(), "Volume Logs", "From time can't be greater than current time!"); + return false; + } - DateTime dateTime_3 = new DateTime(composite, SWT.BORDER | SWT.TIME); - dateTime_3.setBounds(480, 60, 120, 20); - toolkit.adapt(dateTime_3); - toolkit.paintBordersFor(dateTime_3); + if(toTimestamp != null) { + if (toTimestamp.after(now)) { + MessageDialog.openError(getShell(), "Volume Logs", "To time can't be greater than current time!"); + return false; + } + + if(fromTimestamp.after(toTimestamp)) { + MessageDialog.openError(getShell(), "Volume Logs", "From time can't be greater than To time!"); + return false; + } + } - Button btngo = toolkit.createButton(composite, "&Go", SWT.NONE); - btngo.setBounds(605, 55, 60, 30); + return true; + } + + private void createToCheckbox(Composite composite) { + toCheckbox = toolkit.createButton(composite, null, SWT.CHECK); + toCheckbox.setBounds(320, 60, 15, 20); + toCheckbox.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + if (toCheckbox.getSelection()) { + toDate.setEnabled(true); + toTime.setEnabled(true); + } else { + toDate.setEnabled(false); + toTime.setEnabled(false); + } + } + }); + } + + private void createToTimeField(Composite composite) { + toTime = new DateTime(composite, SWT.BORDER | SWT.TIME); + toTime.setBounds(490, 60, 120, 20); + toTime.setEnabled(false); + toolkit.adapt(toTime); + toolkit.paintBordersFor(toTime); + } + + private void createToDateField(Composite composite) { + toDate = new DateTime(composite, SWT.BORDER | SWT.DROP_DOWN); + toDate.setBounds(365, 60, 120, 20); + toDate.setEnabled(false); + toolkit.adapt(toDate); + toolkit.paintBordersFor(toDate); + } + + private void createToDateLabel(Composite composite) { + Label lblTo = toolkit.createLabel(composite, "To", SWT.NONE); + lblTo.setBounds(340, 60, 25, 20); + } + + private void createFromCheckbox(Composite composite) { + fromCheckbox = toolkit.createButton(composite, null, SWT.CHECK); + fromCheckbox.setBounds(0, 60, 15, 20); + fromCheckbox.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + if (fromCheckbox.getSelection()) { + fromDate.setEnabled(true); + fromTime.setEnabled(true); + } else { + fromDate.setEnabled(false); + fromTime.setEnabled(false); + } + } + }); + } + + private void createFromTimeField(Composite composite) { + fromTime = new DateTime(composite, SWT.BORDER | SWT.TIME); + fromTime.setBounds(190, 60, 120, 20); + fromTime.setEnabled(false); + toolkit.adapt(fromTime); + toolkit.paintBordersFor(fromTime); + } + + private void createFromDateField(Composite composite) { + fromDate = new DateTime(composite, SWT.BORDER | SWT.DROP_DOWN); + fromDate.setBounds(60, 60, 120, 20); + fromDate.setEnabled(false); + toolkit.adapt(fromDate); + toolkit.paintBordersFor(fromDate); + } + + private void createFromDateLabel(Composite composite) { + Label lblFrom = toolkit.createLabel(composite, "from", SWT.NONE); + lblFrom.setBounds(20, 60, 40, 20); + } + + private void createSeverityCombo(Composite composite) { + severityCombo = new Combo(composite, SWT.READ_ONLY); + severityCombo.setBounds(555, 15, 110, 20); - Label separator = toolkit.createLabel(composite, "", SWT.SEPARATOR | SWT.HORIZONTAL | SWT.FILL); - separator.setBounds(0, 95, 680, 2); + severityCombo.setItems(GlusterConstants.VOLUME_LOG_LEVELS_ARR.toArray(new String[0])); + severityCombo.select(VOLUME_LOG_LEVELS.ERROR.ordinal()); + severityCombo.add(CoreConstants.ALL, 0); - Label lblFilterString = toolkit.createLabel(composite, "Filter String", SWT.LEFT); - lblFilterString.setBounds(0, 105, 85, 20); + toolkit.adapt(severityCombo); + toolkit.paintBordersFor(severityCombo); + } - text = guiHelper.createFilterText(toolkit, composite); - text.setBounds(90, 105, 250, 20); + private void createSeverityLabel(Composite composite) { + Label lblSeverity = toolkit.createLabel(composite, "Severity", SWT.NONE); + lblSeverity.setBounds(480, 15, 70, 20); + } - Composite tableViewerComposite = createTableViewerComposite(); - - TableViewer tableViewer = new TableViewer(tableViewerComposite, SWT.FLAT | SWT.FULL_SELECTION | SWT.MULTI); - tableViewer.setLabelProvider(new VolumeLogTableLabelProvider()); - tableViewer.setContentProvider(new ArrayContentProvider()); + private void createDisksCombo(Composite composite) { + disksCombo = new Combo(composite, SWT.READ_ONLY); + disksCombo.setBounds(365, 15, 100, 20); + disksCombo.setItems(volume.getDisks().toArray(new String[0])); + disksCombo.add(CoreConstants.ALL, 0); + toolkit.adapt(disksCombo); + toolkit.paintBordersFor(disksCombo); + disksCombo.select(0); + } - setupLogsTable(tableViewerComposite, tableViewer.getTable()); - guiHelper.createFilter(tableViewer, text, false); - tableViewer.setInput(GlusterDummyModel.getDummyLogMessages().toArray()); + private void createDiskLabel(Composite composite) { + Label lblMessagesAndFilter = toolkit.createLabel(composite, "messages, and filter on disk", SWT.NONE); + lblMessagesAndFilter.setBounds(160, 15, 200, 20); + } + + private void createLineCountText(Composite composite) { + lineCountText = toolkit.createText(composite, "100", SWT.NONE); + lineCountText.setBounds(85, 15, 60, 20); + } + + private void createLineCountLabel(Composite composite) { + Label lblScanLast = toolkit.createLabel(composite, "Scan last", SWT.NONE); + lblScanLast.setBounds(0, 15, 80, 20); + } + + private void configureLayout() { + setLayout(new GridLayout(1, false)); + GridData layoutData = new GridData(); + layoutData.grabExcessHorizontalSpace = true; + layoutData.grabExcessVerticalSpace = true; + //layoutData.verticalIndent = 10; + setLayoutData(layoutData); } private Composite createTableViewerComposite() { @@ -193,4 +390,17 @@ public class VolumeLogsPage extends Composite { TableColumnLayout tableColumnLayout = (TableColumnLayout) table.getParent().getLayout(); tableColumnLayout.setColumnData(column, new ColumnWeightData(weight)); } + + private Date extractTimestamp(DateTime date, DateTime time) { + Calendar calendar = Calendar.getInstance(); + calendar.setLenient(false); + calendar.set(Calendar.DAY_OF_MONTH, date.getDay()); + // in Calendar class, month starts with zero i.e. Jan = 0 + calendar.set(Calendar.MONTH, date.getMonth()); + calendar.set(Calendar.YEAR, date.getYear()); + calendar.set(Calendar.HOUR_OF_DAY, time.getHours()); + calendar.set(Calendar.MINUTE, time.getMinutes()); + calendar.set(Calendar.SECOND, time.getSeconds()); + return calendar.getTime(); + } } diff --git a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/details/VolumeOptionsPage.java b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/details/VolumeOptionsPage.java index cd425dc2..c0a69693 100644 --- a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/details/VolumeOptionsPage.java +++ b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/details/VolumeOptionsPage.java @@ -20,29 +20,29 @@ package com.gluster.storage.management.gui.views.details; import java.util.Map.Entry; -import org.eclipse.jface.dialogs.MessageDialog; +import org.apache.commons.lang.WordUtils; import org.eclipse.jface.layout.TableColumnLayout; import org.eclipse.jface.viewers.ArrayContentProvider; -import org.eclipse.jface.viewers.CellEditor; -import org.eclipse.jface.viewers.CheckboxTableViewer; import org.eclipse.jface.viewers.ColumnLabelProvider; import org.eclipse.jface.viewers.ColumnLayoutData; -import org.eclipse.jface.viewers.ColumnViewer; +import org.eclipse.jface.viewers.ColumnViewerToolTipSupport; import org.eclipse.jface.viewers.ColumnWeightData; -import org.eclipse.jface.viewers.EditingSupport; +import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TableViewerColumn; -import org.eclipse.jface.viewers.TextCellEditor; import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.BusyIndicator; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; -import org.eclipse.swt.graphics.Cursor; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Table; @@ -51,9 +51,12 @@ import org.eclipse.swt.widgets.Text; import org.eclipse.ui.forms.widgets.FormToolkit; import com.gluster.storage.management.client.GlusterDataModelManager; -import com.gluster.storage.management.client.VolumesClient; -import com.gluster.storage.management.core.model.Status; +import com.gluster.storage.management.core.constants.CoreConstants; +import com.gluster.storage.management.core.model.DefaultClusterListener; +import com.gluster.storage.management.core.model.Event; +import com.gluster.storage.management.core.model.Event.EVENT_TYPE; import com.gluster.storage.management.core.model.Volume; +import com.gluster.storage.management.core.model.VolumeOptionInfo; import com.gluster.storage.management.gui.VolumeOptionsTableLabelProvider; import com.gluster.storage.management.gui.utils.GUIHelper; @@ -63,36 +66,91 @@ public class VolumeOptionsPage extends Composite { private TableViewer tableViewer; private GUIHelper guiHelper = GUIHelper.getInstance(); private Volume volume; + private DefaultClusterListener clusterListener; + private Text filterText; public enum OPTIONS_TABLE_COLUMN_INDICES { OPTION_KEY, OPTION_VALUE }; private static final String[] OPTIONS_TABLE_COLUMN_NAMES = new String[] { "Option Key", "Option Value" }; + private Button addButton; + private TableViewerColumn keyColumn; + private OptionKeyEditingSupport keyEditingSupport; - public VolumeOptionsPage(Composite parent, int style) { + public VolumeOptionsPage(final Composite parent, int style, Volume volume) { super(parent, style); - addDisposeListener(new DisposeListener() { - public void widgetDisposed(DisposeEvent e) { - toolkit.dispose(); - } - }); + + this.volume = volume; toolkit.adapt(this); toolkit.paintBordersFor(this); setupPageLayout(); - Text filterText = guiHelper.createFilterText(toolkit, this); - setupDiskTableViewer(filterText); + filterText = guiHelper.createFilterText(toolkit, this); + setupOptionsTableViewer(filterText); + + createAddButton(); + + tableViewer.setInput(volume.getOptions().entrySet()); + + parent.layout(); // Important - this actually paints the table + registerListeners(parent); } - public VolumeOptionsPage(final Composite parent, int style, Volume volume) { - this(parent, style); - this.volume = volume; + private void createAddButton() { + addButton = toolkit.createButton(this, "&Add", SWT.FLAT); + addButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + // add an empty option to be filled up by user + volume.setOption("", ""); + + tableViewer.refresh(); + tableViewer.setSelection(new StructuredSelection(getEntry(""))); + keyColumn.getViewer().editElement(getEntry(""), 0); // edit newly created entry + + // disable the add button AND search filter textbox till user fills up the new option + addButton.setEnabled(false); + filterText.setEnabled(false); + } - tableViewer.setInput(volume.getOptions().entrySet().toArray()); + private Entry<String, String> getEntry(String key) { + for(Entry<String, String> entry : volume.getOptions().entrySet()) { + if(entry.getKey().equals(key)) { + return entry; + } + } + return null; + } + }); - parent.layout(); // Important - this actually paints the table + // Make sure that add button is enabled only when search filter textbox is empty + filterText.addModifyListener(new ModifyListener() { + @Override + public void modifyText(ModifyEvent e) { + if(filterText.getText().length() > 0) { + addButton.setEnabled(false); + } else { + addButton.setEnabled(true); + } + } + }); + } + + private void registerListeners(final Composite parent) { + addDisposeListener(new DisposeListener() { + public void widgetDisposed(DisposeEvent e) { + if(!addButton.isEnabled()) { + // user has selected key, but not added value. Since this is not a valid entry, + // remove the last option (without value) from the volume + volume.getOptions().remove(keyEditingSupport.getEntryBeingAdded().getKey()); + } + + GlusterDataModelManager.getInstance().removeClusterListener(clusterListener); + toolkit.dispose(); + } + }); /** * Ideally not required. However the table viewer is not getting laid out properly on performing @@ -105,7 +163,62 @@ public class VolumeOptionsPage extends Composite { parent.layout(); } }); + + parent.addDisposeListener(new DisposeListener() { + + @Override + public void widgetDisposed(DisposeEvent e) { + if(!addButton.isEnabled()) { + // user has selected key, but not added value. Since this is not a valid entry, + // remove the last option (without value) from the volume + Entry<String, String> entryBeingAdded = keyEditingSupport.getEntryBeingAdded(); + volume.getOptions().remove(entryBeingAdded.getKey()); + } + } + }); + + clusterListener = new DefaultClusterListener() { + @SuppressWarnings("unchecked") + @Override + public void volumeChanged(Volume volume, Event event) { + super.volumeChanged(volume, event); + if(event.getEventType() == EVENT_TYPE.VOLUME_OPTIONS_RESET) { + if(!tableViewer.getControl().isDisposed()) { + tableViewer.refresh(); + } + } + + if(event.getEventType() == EVENT_TYPE.VOLUME_OPTION_SET) { + Entry<String, String> eventEntry = (Entry<String, String>)event.getEventData(); + if (isNewOption(volume, eventEntry.getKey())) { + // option has been set successfully by the user. re-enable the add button and search filter textbox + addButton.setEnabled(true); + filterText.setEnabled(true); + } + + if(tableViewer.getTable().getItemCount() < volume.getOptions().size()) { + // new volume set from outside this page. refresh the viewer. + tableViewer.refresh(); + } else { + // existing volume option value changed. update that element. + tableViewer.update(eventEntry, null); + } + } + } + + private boolean isNewOption(Volume volume, String optionKey) { + if(filterText.getText().length() > 0) { + // user has been filtering the contents. adding new option is allowed only when contents are NOT + // filtered. Thus it's impossible that this is a newly added option + return false; + } + + // if this is the last option in the volume options, it must be the new option + return optionKey.equals(volume.getOptions().keySet().toArray()[volume.getOptions().size()-1]); + } + }; + GlusterDataModelManager.getInstance().addClusterListener(clusterListener); } private void setupPageLayout() { @@ -115,7 +228,7 @@ public class VolumeOptionsPage extends Composite { setLayout(layout); } - private void setupDiskTable(Composite parent) { + private void setupOptionsTable(Composite parent) { Table table = tableViewer.getTable(); table.setHeaderVisible(true); table.setLinesVisible(false); @@ -137,64 +250,12 @@ public class VolumeOptionsPage extends Composite { return tableColumnLayout; } - private class OptionValueEditingSupport extends EditingSupport { - private CellEditor cellEditor; - - public OptionValueEditingSupport(ColumnViewer viewer) { - super(viewer); - cellEditor = new TextCellEditor((Composite) viewer.getControl()); - } - - @Override - protected void setValue(final Object element, final Object value) { - final Entry<String, String> entry = (Entry<String, String>)element; - if(entry.getValue().equals(value)) { - // value is same as that present in the model. return without doing anything. - return; - } - - final Cursor oldCursor = getViewer().getControl().getCursor(); - //getViewer().getControl().setCursor(new Cursor(Display.getDefault(), SWT.CURSOR_WAIT)); - // value has changed. set volume option at back-end and update model accordingly - BusyIndicator.showWhile(getDisplay(), new Runnable() { - - @Override - public void run() { - VolumesClient client = new VolumesClient(GlusterDataModelManager.getInstance().getSecurityToken()); - Status status = client.setVolumeOption(volume.getName(), entry.getKey(), (String)value); - if(status.isSuccess()) { - volume.setOption(entry.getKey(), (String)value); - } else { - MessageDialog.openError(getShell(), "Set Volume Option", status.getMessage()); - } - getViewer().update(entry, null); - //getViewer().refresh(); - //getViewer().getControl().setCursor(oldCursor); - } - }); - } - - @Override - protected Object getValue(Object element) { - return ((Entry<String, String>) element).getValue(); - } - - @Override - protected CellEditor getCellEditor(Object element) { - return cellEditor; - } - - @Override - protected boolean canEdit(Object element) { - return true; - } - } - private TableColumn createValueColumn() { TableViewerColumn valueColumn = new TableViewerColumn(tableViewer, SWT.NONE); valueColumn.getColumn() .setText(OPTIONS_TABLE_COLUMN_NAMES[OPTIONS_TABLE_COLUMN_INDICES.OPTION_VALUE.ordinal()]); valueColumn.setLabelProvider(new ColumnLabelProvider() { + @SuppressWarnings("unchecked") @Override public String getText(Object element) { return ((Entry<String, String>) element).getValue(); @@ -202,30 +263,50 @@ public class VolumeOptionsPage extends Composite { }); // User can edit value of a volume option - valueColumn.setEditingSupport(new OptionValueEditingSupport(valueColumn.getViewer())); + valueColumn.setEditingSupport(new OptionValueEditingSupport(valueColumn.getViewer(), volume)); return valueColumn.getColumn(); } private TableColumn createKeyColumn() { - TableViewerColumn keyColumn = new TableViewerColumn(tableViewer, SWT.NONE); + keyColumn = new TableViewerColumn(tableViewer, SWT.NONE); keyColumn.getColumn().setText(OPTIONS_TABLE_COLUMN_NAMES[OPTIONS_TABLE_COLUMN_INDICES.OPTION_KEY.ordinal()]); keyColumn.setLabelProvider(new ColumnLabelProvider() { + @SuppressWarnings("unchecked") @Override public String getText(Object element) { return ((Entry<String, String>) element).getKey(); } + + @SuppressWarnings("unchecked") + @Override + public String getToolTipText(Object element) { + String key = ((Entry<String, String>) element).getKey(); + if(key.isEmpty()) { + return "Click to select a volume option key"; + } + + VolumeOptionInfo optionInfo = GlusterDataModelManager.getInstance().getVolumeOptionInfo(key); + // Wrap the description before adding to tooltip so that long descriptions are displayed properly + return WordUtils.wrap(optionInfo.getDescription(), 60) + CoreConstants.NEWLINE + "Default value: " + + optionInfo.getDefaultValue(); + } }); + + // Editing support required when adding new key + keyEditingSupport = new OptionKeyEditingSupport(keyColumn.getViewer(), volume); + keyColumn.setEditingSupport(keyEditingSupport); + return keyColumn.getColumn(); } - private void createDiskTableViewer(Composite parent) { - tableViewer = CheckboxTableViewer.newCheckList(parent, SWT.FLAT | SWT.FULL_SELECTION | SWT.MULTI); - // TableViewer tableViewer = new TableViewer(parent, SWT.FLAT | SWT.FULL_SELECTION | SWT.MULTI); + private void createOptionsTableViewer(Composite parent) { + tableViewer = new TableViewer(parent, SWT.FLAT | SWT.FULL_SELECTION | SWT.SINGLE); tableViewer.setLabelProvider(new VolumeOptionsTableLabelProvider()); tableViewer.setContentProvider(new ArrayContentProvider()); + tableViewer.getTable().setLinesVisible(true); - setupDiskTable(parent); + setupOptionsTable(parent); } private Composite createTableViewerComposite() { @@ -235,22 +316,15 @@ public class VolumeOptionsPage extends Composite { return tableViewerComposite; } - private void setupDiskTableViewer(final Text filterText) { + private void setupOptionsTableViewer(final Text filterText) { Composite tableViewerComposite = createTableViewerComposite(); - createDiskTableViewer(tableViewerComposite); + createOptionsTableViewer(tableViewerComposite); + ColumnViewerToolTipSupport.enableFor(tableViewer); // Create a case insensitive filter for the table viewer using the filter text field guiHelper.createFilter(tableViewer, filterText, false); } - /** - * Sets properties for alignment and weight of given column of given table - * - * @param table - * @param columnIndex - * @param alignment - * @param weight - */ - public void setColumnProperties(Table table, OPTIONS_TABLE_COLUMN_INDICES columnIndex, int alignment, int weight) { + private void setColumnProperties(Table table, OPTIONS_TABLE_COLUMN_INDICES columnIndex, int alignment, int weight) { TableColumn column = table.getColumn(columnIndex.ordinal()); column.setAlignment(alignment); diff --git a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/details/tabcreators/VolumeTabCreator.java b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/details/tabcreators/VolumeTabCreator.java index 7f78829e..6913e211 100644 --- a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/details/tabcreators/VolumeTabCreator.java +++ b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/views/details/tabcreators/VolumeTabCreator.java @@ -255,7 +255,7 @@ public class VolumeTabCreator implements TabCreator { private void createVolumeOptionsTab(Volume volume, TabFolder tabFolder, FormToolkit toolkit) { Composite volumeTab = guiHelper.createTab(tabFolder, "Options", IImageKeys.VOLUME); - VolumeOptionsPage page = new VolumeOptionsPage(volumeTab, SWT.NONE, volume); + //VolumeOptionsPage page = new VolumeOptionsPage(volumeTab, SWT.NONE, volume); volumeTab.layout(); // IMP: lays out the form properly } diff --git a/src/com.gluster.storage.management.server.scripts/src/common/Common.py b/src/com.gluster.storage.management.server.scripts/src/common/Common.py index 60f200fe..99c2f440 100644 --- a/src/com.gluster.storage.management.server.scripts/src/common/Common.py +++ b/src/com.gluster.storage.management.server.scripts/src/common/Common.py @@ -32,3 +32,12 @@ def log(priority, message=None): else:
syslog.syslog(logPriority, logMessage)
return
+
+
+def stripEmptyLines(content):
+ ret = ""
+ for line in content.split("\n"):
+ if line.strip() != "":
+ ret += line
+ return ret
+
diff --git a/src/com.gluster.storage.management.server.scripts/src/common/DiskUtils.py b/src/com.gluster.storage.management.server.scripts/src/common/DiskUtils.py new file mode 100644 index 00000000..0e42bba2 --- /dev/null +++ b/src/com.gluster.storage.management.server.scripts/src/common/DiskUtils.py @@ -0,0 +1,226 @@ +# Copyright (c) 2010 Gluster, Inc. <http://www.gluster.com> +# This file is part of Gluster Storage Platform. +# +# Gluster Storage Platform 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 Storage Platform 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/>. + +import os +import glob +import dbus + +import Globals +from Utils import * + +ONE_MB_SIZE = 1048576 + + +def _stripDev(device): + if isString(device) and device.startswith("/dev/"): + return device[5:] + return device + + +def _addDev(deviceName): + if isString(deviceName) and not deviceName.startswith("/dev/"): + return "/dev/" + deviceName + return deviceName + + +def getDeviceName(device): + if type(device) == type([]): + nameList = [] + for d in device: + nameList.append(_stripDev(d)) + return nameList + return _stripDev(device) + + +def getDevice(deviceName): + if isString(deviceName): + return _addDev(deviceName) + if type(deviceName) == type([]): + nameList = [] + for d in deviceName: + nameList.append(_addDev(d)) + return nameList + return _addDev(deviceName) + + +def getDiskPartitionByUuid(uuid): + uuidFile = "/dev/disk/by-uuid/%s" % uuid + if os.path.exists(uuidFile): + return getDeviceName(os.path.realpath(uuidFile)) + return None + + +def getUuidByDiskPartition(device): + for uuidFile in glob.glob("/dev/disk/by-uuid/*"): + if os.path.realpath(uuidFile) == device: + return os.path.basename(uuidFile) + return None + + +def getDiskPartitionUuid(partition): + log("WARNING: getDiskPartitionUuid() is deprecated by getUuidByDiskPartition()") + return getUuidByDiskPartition(partition) + + +def getDiskPartitionByLabel(label): + ## TODO: Finding needs to be enhanced + labelFile = "/dev/disk/by-label/%s" % label + if os.path.exists(labelFile): + return getDeviceName(os.path.realpath(labelFile)) + return None + + +def getDeviceByLabel(label): + log("WARNING: getDeviceByLabel() is deprecated by getDiskPartitionByLabel()") + return getDiskPartitionByLabel(label) + + +def getDiskPartitionLabel(device): + rv = runCommandFG(["sudo", "e2label", device], stdout=True) + if rv["Status"] == 0: + return rv["Stdout"].strip() + return False + + +def getRootPartition(fsTabFile=Globals.FSTAB_FILE): + fsTabEntryList = readFsTab(fsTabFile) + for fsTabEntry in fsTabEntryList: + if fsTabEntry["MountPoint"] == "/": + if fsTabEntry["Device"].startswith("UUID="): + return getDiskPartitionByUuid(fsTabEntry["Device"].split("UUID=")[-1]) + if fsTabEntry["Device"].startswith("LABEL="): + return getDiskPartitionByLabel(fsTabEntry["Device"].split("LABEL=")[-1]) + return getDeviceName(fsTabEntry["Device"]) + return None + + +def getOsDisk(): + log("WARNING: getOsDisk() is deprecated by getRootPartition()") + return getRootPartition() + + +def getDiskList(diskDeviceList=None): + diskDeviceList = getDevice(diskDeviceList) + if isString(diskDeviceList): + diskDeviceList = [diskDeviceList] + + dbusSystemBus = dbus.SystemBus() + halObj = dbusSystemBus.get_object("org.freedesktop.Hal", + "/org/freedesktop/Hal/Manager") + halManager = dbus.Interface(halObj, "org.freedesktop.Hal.Manager") + storageUdiList = halManager.FindDeviceByCapability("storage") + + diskList = [] + for udi in storageUdiList: + halDeviceObj = dbusSystemBus.get_object("org.freedesktop.Hal", udi) + halDevice = dbus.Interface(halDeviceObj, + "org.freedesktop.Hal.Device") + if halDevice.GetProperty("storage.drive_type") == "cdrom" or \ + halDevice.GetProperty("block.is_volume"): + continue + + disk = {} + disk["Device"] = str(halDevice.GetProperty('block.device')) + if diskDeviceList and disk["Device"] not in diskDeviceList: + continue + disk["Description"] = str(halDevice.GetProperty('storage.vendor')) + " " + str(halDevice.GetProperty('storage.model')) + if halDevice.GetProperty('storage.removable'): + disk["Size"] = long(halDevice.GetProperty('storage.removable.media_size')) + else: + disk["Size"] = long(halDevice.GetProperty('storage.size')) + disk["Interface"] = str(halDevice.GetProperty('storage.bus')) + disk["DriveType"] = str(halDevice.GetProperty('storage.drive_type')) + partitionList = [] + partitionUdiList = halManager.FindDeviceStringMatch("info.parent", udi) + for partitionUdi in partitionUdiList: + partitionHalDeviceObj = dbusSystemBus.get_object("org.freedesktop.Hal", + partitionUdi) + partitionHalDevice = dbus.Interface(partitionHalDeviceObj, + "org.freedesktop.Hal.Device") + if not partitionHalDevice.GetProperty("block.is_volume"): + continue + partition = {} + partition["Device"] = str(partitionHalDevice.GetProperty('block.device')) + partition["Uuid"] = str(partitionHalDevice.GetProperty('volume.uuid')) + partition["Size"] = long(partitionHalDevice.GetProperty('volume.size')) + partition["Fstype"] = str(partitionHalDevice.GetProperty('volume.fstype')) + partition["Fsversion"] = str(partitionHalDevice.GetProperty('volume.fsversion')) + partition["Label"] = str(partitionHalDevice.GetProperty('volume.label')) + partition["Used"] = 0L + if partitionHalDevice.GetProperty("volume.is_mounted"): + rv = runCommandFG(["df", str(partitionHalDevice.GetProperty('volume.mount_point'))], stdout=True) + if rv["Status"] == 0: + try: + partition["Used"] = long(rv["Stdout"].split("\n")[1].split()[2]) + except IndexError: + pass + except ValueError: + pass + partitionList.append(partition) + disk["Partitions"] = partitionList + diskList.append(disk) + return diskList + +def readFsTab(fsTabFile=Globals.FSTAB_FILE): + try: + fsTabfp = open(fsTabFile) + except IOError, e: + Utils.log("readFsTab(): " + str(e)) + return None + + fsTabEntryList = [] + for line in fsTabfp: + tokens = line.strip().split() + if not tokens or tokens[0].startswith('#'): + continue + fsTabEntry = {} + fsTabEntry["Device"] = None + fsTabEntry["MountPoint"] = None + fsTabEntry["FsType"] = None + fsTabEntry["Options"] = None + fsTabEntry["DumpOption"] = 0 + fsTabEntry["fsckOrder"] = 0 + try: + fsTabEntry["Device"] = tokens[0] + fsTabEntry["MountPoint"] = tokens[1] + fsTabEntry["FsType"] = tokens[2] + fsTabEntry["Options"] = tokens[3] + fsTabEntry["DumpOption"] = tokens[4] + fsTabEntry["fsckOrder"] = tokens[5] + except IndexError: + pass + if fsTabEntry["Device"] and fsTabEntry["MountPoint"] and fsTabEntry["FsType"] and fsTabEntry["Options"]: + fsTabEntryList.append(fsTabEntry) + + fsTabfp.close() + return fsTabEntryList + + +def getMountPointByUuid(partitionUuid): + # check uuid in etc/fstab + try: + fstabEntries = open(Globals.FSTAB_FILE).readlines() + except IOError: + fstabEntries = [] + found = False + for entry in fstabEntries: + entry = entry.strip() + if not entry: + continue + if entry.split()[0] == "UUID=" + partitionUuid: + return entry.split()[1] + return None diff --git a/src/com.gluster.storage.management.server.scripts/src/common/Utils.py b/src/com.gluster.storage.management.server.scripts/src/common/Utils.py index c605eecd..5140b641 100644 --- a/src/com.gluster.storage.management.server.scripts/src/common/Utils.py +++ b/src/com.gluster.storage.management.server.scripts/src/common/Utils.py @@ -34,7 +34,7 @@ import urllib import Globals import Protocol - +from Common import * RUN_COMMAND_ERROR = -1024 LOG_SYSLOG = 1 @@ -117,8 +117,7 @@ def openLog(fileName=None): return False return True - -def log(priority, message=None): +def record(priority, message=None): global LOG_FILE_OBJ global SYSLOG_REQUIRED diff --git a/src/com.gluster.storage.management.server.scripts/src/nodes/CreateVolumeExportDirectory.py b/src/com.gluster.storage.management.server.scripts/src/nodes/CreateVolumeExportDirectory.py deleted file mode 100644 index 611d9695..00000000 --- a/src/com.gluster.storage.management.server.scripts/src/nodes/CreateVolumeExportDirectory.py +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/python -# Copyright (C) 2010 Gluster, Inc. <http://www.gluster.com> -# This file is part of Gluster Storage Platform. -# -# Gluster Storage Platform 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 Storage Platform 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/>. -import os -from XmlHandler import ResponseXml -from optparse import OptionParser -import Utils - -def stripEmptyLines(content): - ret = "" - for line in content.split("\n"): - if line.strip() != "": - ret += line - return ret - -def createDirectory(disk, volumename): - dirname = "/export" - if not os.path.isdir(dirname) or not os.path.isdir(disk): - rs = ResponseXml() - rs.appendTagRoute("code", 1) - rs.appendTagRoute("message", "Disk is not mounted properly") - return rs.toprettyxml() - - - if not os.path.isdir(dirname + "/" + disk + "/" + volumename + "/"): - command = "mkdir " + volumename; - rv = Utils.runCommandFG(command, stdout=True, root=True) - message = stripEmptyLines(rv["Stdout"]) - if rv["Stderr"]: - message += "Error: [" + stripEmptyLines(rv["Stderr"]) + "]" - rs = ResponseXml() - rs.appendTagRoute("status.code", rv["Status"]) - rs.appendTagRoute("status.message", message) - return rs.toprettyxml() - -def main(disk, volumename): - return createDirectory(disk, volumename)
\ No newline at end of file diff --git a/src/com.gluster.storage.management.server.scripts/src/nodes/GlusterdUtils.py b/src/com.gluster.storage.management.server.scripts/src/nodes/GlusterdUtils.py new file mode 100644 index 00000000..7c0e899c --- /dev/null +++ b/src/com.gluster.storage.management.server.scripts/src/nodes/GlusterdUtils.py @@ -0,0 +1,250 @@ +# Copyright (c) 2010 Gluster, Inc. <http://www.gluster.com> +# This file is part of Gluster Storage Platform. +# +# Gluster Storage Platform 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 Storage Platform 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/>. + +import os +import Utils + +import ServerUtils + + +def getGlusterVolumeInfo(volumeName=None): + volumeNameList = None + if Utils.isString(volumeName): + volumeNameList = [volumeName] + if type(volumeName) == type([]): + volumeNameList = volumeName + + status = Utils.runCommand("gluster volume info", output=True, root=True) + if status["Status"] != 0: + Utils.log("Failed to execute 'gluster volume info' command") + return None + + volumeInfoDict = {} + volumeInfo = {} + volumeName = None + brickList = [] + for line in status['Stdout'].split("\n"): + if not line: + if volumeName and volumeInfo: + volumeInfo["Bricks"] = brickList + volumeInfoDict[volumeName] = volumeInfo + volumeInfo = {} + volumeName = None + brickList = [] + continue + + tokens = line.split(":") + if tokens[0].strip().upper() == "BRICKS": + continue + elif tokens[0].strip().upper() == "VOLUME NAME": + volumeName = tokens[1].strip() + volumeInfo["VolumeName"] = volumeName + elif tokens[0].strip().upper() == "TYPE": + volumeInfo["VolumeType"] = tokens[1].strip() + elif tokens[0].strip().upper() == "STATUS": + volumeInfo["VolumeStatus"] = tokens[1].strip() + elif tokens[0].strip().upper() == "TRANSPORT-TYPE": + volumeInfo["TransportType"] = tokens[1].strip() + elif tokens[0].strip().upper().startswith("BRICK"): + brickList.append(":".join(tokens[1:]).strip()) + + if volumeName and volumeInfo: + volumeInfoDict[volumeName] = volumeInfo + + if not volumeNameList: + return volumeInfoDict + + # remove unwanted volume info + for volumeName in list(set(volumeInfoDict.keys()) - set(volumeNameList)): + del volumeInfoDict[volumeName] + + return volumeInfoDict + + +def isVolumeRunning(volumeName): + if not volumeName: + return False + volumeInfo = getGlusterVolumeInfo(volumeName) + if not volumeInfo: + return False + status = volumeInfo[volumeName]["VolumeStatus"] + if not status: + return False + if status.upper() == "STARTED": + return True + return False + + +def isVolumeExist(volumeName): + if not volumeName: + return False + if getGlusterVolumeInfo(volumeName): + return True + return False + + +def peerProbe(serverName): + command = "gluster peer probe %s" % serverName + status = Utils.runCommand(command, output=True, root=True) + if status["Status"] == 0: + return True + Utils.log("command [%s] failed with [%d:%s]" % (command, status["Status"], os.strerror(status["Status"]))) + return False + + +def setAuthAllow(volumeName, authList, includeServers=True): + if not (volumeName and authList): + return False + vacl = [] + if includeServers: + for serverName in ServerUtils.getAllServerList(): + vacl += ServerUtils.getServerIpList(serverName) + vacl += authList + + command = "gluster volume set %s auth.allow %s" % (volumeName, ",".join(list(set(vacl)))) + status = Utils.runCommand(command, output=True, root=True) + if status["Status"] == 0: + return True + Utils.log("command [%s] failed with [%d:%s]" % (command, status["Status"], os.strerror(status["Status"]))) + return False + + +def volumeCreate(volumeName, volumeType, transportTypeList, brickList): + command = "gluster volume create %s" % volumeName + + if volumeType.upper() == "MIRROR": + command += " replica 2" + elif volumeType.upper() == "STRIPE": + command += " stripe 4" + + if "RDMA" in transportTypeList: + command += " transport rdma" + + command += " " + " ".join(brickList) + + status = Utils.runCommand(command, output=True, root=True) + if status["Status"] == 0: + return True + Utils.log("command [%s] failed with [%d:%s]" % (command, status["Status"], os.strerror(status["Status"]))) + return False + + +def volumeDelete(volumeName): + command = "gluster --mode=script volume delete %s" % volumeName + status = Utils.runCommand(command, output=True, root=True) + if status["Status"] == 0: + return True + Utils.log("command [%s] failed with [%d:%s]" % (command, status["Status"], os.strerror(status["Status"]))) + return False + + +def volumeLogFileName(volumeName, brick, logDir): + command = "gluster volume log filename %s %s %s" % (volumeName, brick, logDir) + status = Utils.runCommand(command, output=True, root=True) + if status["Status"] == 0: + return True + Utils.log("command [%s] failed with [%d:%s]" % (command, status["Status"], os.strerror(status["Status"]))) + return False + + +def startVolumeMigration(volumeName, sourcePath, destinationPath): + command = "gluster volume replace-brick %s %s %s start" % (volumeName, sourcePath, destinationPath) + status = Utils.runCommand(command, output=True, root=True) + if status["Status"] == 0: + lines = status["Stdout"].split("\n") + if lines[0].split()[-1] == "successfully": + return True + Utils.log("command [%s] failed with [%d:%s]" % (command, status["Status"], os.strerror(status["Status"]))) + return False + + +def stopVolumeMigration(volumeName, sourcePath, destinationPath): + command = "gluster volume replace-brick %s %s %s abort" % (volumeName, sourcePath, destinationPath) + status = Utils.runCommand(command, output=True, root=True) + if status["Status"] == 0: + lines = status["Stdout"].split("\n") + if lines[0].split()[-1] == "successful": + return True + Utils.log("command [%s] failed with [%d:%s]" % (command, status["Status"], os.strerror(status["Status"]))) + return False + + +def commitVolumeMigration(volumeName, sourcePath, destinationPath): + command = "gluster volume replace-brick %s %s %s commit" % (volumeName, sourcePath, destinationPath) + status = Utils.runCommand(command, output=True, root=True) + if status["Status"] == 0: + lines = status["Stdout"].split("\n") + if lines[0].split()[-1] == "successful": + return True + Utils.log("command [%s] failed with [%d:%s]" % (command, status["Status"], os.strerror(status["Status"]))) + return False + + +def getMigrationStatus(volumeName, sourcePath, destinationPath): + command = "gluster volume replace-brick %s %s %s status" % (volumeName, sourcePath, destinationPath) + status = Utils.runCommand(command, output=True, root=True) + if status['Status'] == 0 and status['Stdout']: + lines = status["Stdout"].split("\n") + if "Current file" in lines[0]: + return "started" + if "Migration complete" in lines[0]: + return "completed" + Utils.log("command [%s] returns unknown status:%s" % (command, lines[0])) + return "failed" + #if status['Status'] == 0 and status['Stdout']: + # for line in status['Stdout'].split('\n'): + # words = line.split() + # if words and words[0].upper() == "STATUS:": + # return " ".join(words[1:]).upper() + Utils.log("command [%s] failed with [%d:%s]" % (command, status["Status"], os.strerror(status["Status"]))) + return None + + +def volumeRebalanceStart(volumeName): + command = "gluster volume rebalance %s start" % volumeName + status = Utils.runCommand(command, output=True, root=True) + if status["Status"] == 0: + lines = status["Stdout"].split("\n") + if lines[0].split()[-1] == "successful": + return True + Utils.log("command [%s] failed with [%d:%s]" % (command, status["Status"], os.strerror(status["Status"]))) + return False + + +def volumeRebalanceStop(volumeName): + command = "gluster volume rebalance %s stop" % volumeName + status = Utils.runCommand(command, output=True, root=True) + if status["Status"] == 0: + lines = status["Stdout"].split("\n") + if lines[0].split()[0] == "stopped": + return True + Utils.log("command [%s] failed with [%d:%s]" % (command, status["Status"], os.strerror(status["Status"]))) + return False + + +def volumeRebalanceStatus(volumeName): + command = "gluster volume rebalance %s status" % volumeName + status = Utils.runCommand(command, output=True, root=True) + if status["Status"] == 0: + lines = status["Stdout"].split("\n") + if "rebalance not started" in lines[0]: + return "not started" + if "rebalance completed" in lines[0]: + return "completed" + return "running" + Utils.log("command [%s] failed with [%d:%s]" % (command, status["Status"], os.strerror(status["Status"]))) + return False diff --git a/src/com.gluster.storage.management.server.scripts/src/nodes/VolumeUtils.py b/src/com.gluster.storage.management.server.scripts/src/nodes/VolumeUtils.py new file mode 100644 index 00000000..a19ccd62 --- /dev/null +++ b/src/com.gluster.storage.management.server.scripts/src/nodes/VolumeUtils.py @@ -0,0 +1,610 @@ +# Copyright (c) 2010 Gluster, Inc. <http://www.gluster.com> +# This file is part of Gluster Storage Platform. +# +# Gluster Storage Platform 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 Storage Platform 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/>. + +import os +import glob +import tempfile +from operator import itemgetter +import Globals +from Protocol import * +from Utils import * +from DiskUtils import * +from ServerUtils import * +import GlusterdUtils as Glusterd + + +def isVolumeExist(volumeName): + volumeDom = XDOM() + return volumeDom.parseFile("%s/%s.xml" % (Globals.VOLUME_CONF_DIR, volumeName)) and \ + Glusterd.isVolumeExist(volumeName) + + +def getVolumeUuid(volumeName): + fileName = "%s/%s.xml" % (Globals.VOLUME_CONF_DIR, volumeName) + volumeDom = XDOM() + if not volumeDom.parseFile(fileName): + log("Failed to parse volume configuration file %s of %s" % (fileName, volumeName)) + return None + return volumeDom.getTextByTagRoute("uuid") + + +def readVolumeSmbConfFile(fileName=Globals.VOLUME_SMBCONF_FILE): + entryList = [] + try: + fp = open(fileName) + for line in fp: + tokens = line.split("#")[0].strip().split(";")[0].strip().split("=") + if len(tokens) != 2: + continue + if tokens[0].strip().upper() == "INCLUDE": + entryList.append(tokens[1].strip()) + fp.close() + except IOError, e: + log("Failed to open file %s: %s" % (fileName, str(e))) + return entryList + + +def writeVolumeSmbConfFile(entryList, fileName=Globals.VOLUME_SMBCONF_FILE): + try: + fp = open(fileName, "w") + for entry in entryList: + fp.write("include = %s\n" % entry) + fp.close() + return True + except IOError, e: + log("Failed to write file %s: %s" % (fileName, str(e))) + return False + + +def includeVolume(volumeName, fileName=Globals.VOLUME_SMBCONF_FILE): + volumeFile = "%s/%s.smbconf" % (Globals.VOLUME_CONF_DIR, volumeName) + if not os.path.exists(volumeFile): + return False + entryList = readVolumeSmbConfFile(fileName) + if volumeFile in entryList: + return True + entryList.append(volumeFile) + return writeVolumeSmbConfFile(entryList, fileName) + + +def excludeVolume(volumeName, fileName=Globals.VOLUME_SMBCONF_FILE): + volumeFile = "%s/%s.smbconf" % (Globals.VOLUME_CONF_DIR, volumeName) + if not os.path.exists(volumeFile): + return False + entryList = readVolumeSmbConfFile(fileName) + if volumeFile not in entryList: + return True + entryList.remove(volumeFile) + log("entryList = %s" % entryList) + return writeVolumeSmbConfFile(entryList, fileName) + + +def writeVolumeCifsConfiguration(volumeName, userList, adminUser=None): + volumeFile = "%s/%s.smbconf" % (Globals.VOLUME_CONF_DIR, volumeName) + try: + fp = open(volumeFile, "w") + fp.write("[%s]\n" % volumeName) + fp.write(" comment = %s volume served by Gluster\n" % volumeName) + fp.write(" path = %s/%s\n" % (Globals.CIFS_EXPORT_DIR, volumeName)) + fp.write(" guest ok = yes\n") + fp.write(" public = yes\n") + fp.write(" writable = yes\n") + if adminUser: + fp.write(" admin users = %s, %s\n" % (adminUser, ", ".join(userList))) + fp.write(" valid users = %s, %s\n" % (adminUser, ", ".join(userList))) + else: + fp.write(" admin users = %s\n" % (", ".join(userList))) + fp.write(" valid users = %s\n" % (", ".join(userList))) + fp.close() + return True + except IOError, e: + log("Failed to write file %s: %s" % (volumeFile, str(e))) + return False + + +def removeVolumeCifsConfiguration(volumeName): + volumeFile = "%s/%s.smbconf" % (Globals.VOLUME_CONF_DIR, volumeName) + try: + os.remove(volumeFile) + except OSError, e: + log("Failed to remove file %s: %s" % (volumeFile, str(e))) + + +def getVolumeListByPartitionName(partitionName): + volumeConfigFileList = glob.glob(Globals.VOLUME_CONF_DIR + "/*.xml") + if not volumeConfigFileList: + return None + + volumeList = [] + for volumeXmlFile in volumeConfigFileList: + volumeDom = XDOM() + volumeDom.parseFile(volumeXmlFile) + serverTopology = volumeDom.getElementsByTagRoute("volume.topology.group") + serverPartitionFound = False + for topology in serverTopology: + partitionDom = XDOM() + for partition in topology.getElementsByTagName("partition"): + partitionDom.setDomObj(partition) + if partitionDom.getTextByTagRoute("name") == partitionName: + serverPartitionFound = True + break + if serverPartitionFound: + volumeList.append(volumeDom.getElementsByTagRoute("volume")[0]) + break + return volumeList + + +def addServerPartitionConfig(inputDom, groupOrder, partitionTag): + if not(inputDom and groupOrder and partitionTag): + return False + groupDom = XDOM() + for group in inputDom.getElementsByTagRoute("topology.group"): + groupDom.setDomObj(group) + order = groupDom.getTextByTagRoute("order") + if order and int(order) == groupOrder: + group.appendChild(partitionTag) + return inputDom + return False + + +def removeServerPartitionConfig(inputDom, partitionName): + if not(inputDom and partitionName): + return False + for group in inputDom.getElementsByTagRoute("topology.group"): + partitionDom = XDOM() + for partition in group.getElementsByTagName("partition"): + partitionDom.setDomObj(partition) + if partitionDom.getTextByTagRoute("name") == partitionName: + group.removeChild(partition) + return inputDom + return False + + +def updateServerPartitionConfig(inputDom, partitionName, partitionTag): + if not(inputDom and partitionName and partitionTag): + return False + for group in inputDom.getElementsByTagRoute("topology.group"): + partitionDom = XDOM() + for partition in group.getElementsByTagName("partition"): + partitionDom.setDomObj(partition) + if partitionDom.getTextByTagRoute("name") == partitionName: + try: + group.replaceChild(partitionTag, partition) + return inputDom + except AttributeError: + return False + return False + + +def getServerPartitionConfigUuid(serverGroupList, serverPartition): + for group in serverGroupList: + if not group: + continue + partitionDom = XDOM() + for partition in group.getElementsByTagName("partition"): + partitionDom.setDomObj(partition) + partitionName = partition.getTextByTagName("name") + if not partitionName: + continue + if partitionName == serverPartition: + return partitionDom.getTextByTagName("uuid") + return False + + +def setServerPartitionConfigProperty(inputDom, partitionName, propertyDict): + if not(inputDom and partitionName and propertyDict): + return False + for group in inputDom.getElementsByTagRoute("topology.group"): + partitionDom = XDOM() + for partition in group.getElementsByTagName("partition"): + partitionDom.setDomObj(partition) + if partitionDom.getTextByTagRoute("name") == partitionName: + for part in propertyDict.keys(): + x = partition.getElementsByTagName(part) + if x: + x[0].childNodes[0].nodeValue = propertyDict[part] + return inputDom + return False + + +def getSortedServerPartitionConfigProperty(inputDom): + groupDict = {} + if not inputDom: + return None + groupDom = XDOM() + for group in inputDom.getElementsByTagRoute("topology.group"): + groupDom.setDomObj(group) + groupOrder = groupDom.getTextByTagRoute("order") + if not groupOrder: + return None + groupOrder = int(groupOrder) + if groupOrder < 1: + return None + partitionDom = XDOM() + partitionDict = {} + for partition in group.getElementsByTagName("partition"): + partitionDom.setDomObj(partition) + partitionName = partitionDom.getTextByTagRoute("name") + if not partitionName: + return None + partitionOrder = partitionDom.getTextByTagRoute("order") + if not partitionOrder: + return None + partitionUuid = partitionDom.getTextByTagRoute("uuid") + partitionOrder = int(partitionOrder) + if partitionOrder < 1: + return None + partitionDetails = partitionName.split(":") + if not partitionDetails or len(partitionDetails) < 1: + return None + partitionDict[partitionOrder] = { "order":partitionOrder, + "servername":partitionDetails[0], + "name":partitionDetails[1], + "uuid":partitionUuid} + groupDict[groupOrder] = partitionDict + + serverList = [] + groupOrderList = groupDict.keys() + groupOrderList.sort() + for groupOrder in groupOrderList: + partitionOrderList = groupDict[groupOrder].keys() + partitionOrderList.sort() + for partitionOrder in partitionOrderList: + serverList.append(groupDict[groupOrder][partitionOrder]) + + return serverList + + +def getSortedServerPartitionList(serverGroupElements): + serverPartitionDict = {} + groupOrderList = [] + serverList = [] + partitionDom = XDOM() + for group in serverGroupElements: + if not group: + continue + groupOrderE = group.getElementsByTagName("order") + if not (groupOrderE and groupOrderE[0].childNodes): + return None + value = int(XDOM.getText(groupOrderE[0].childNodes)) + if value > 0: + groupOrderList.append(value) + partitionDict = {} + for partition in group.getElementsByTagName("partition"): + partitionDom.setDomObj(partition) + + partitionName = partitionDom.getTextByTagRoute("name") + if not partitionName: + return None + partitionOrder = partitionDom.getTextByTagRoute("order") + if not partitionOrder: + return None + partitionUuid = partitionDom.getTextByTagRoute("uuid") + partitionDict[int(partitionOrder)] = [partitionName, partitionUuid] + serverPartitionDict[value] = partitionDict + groupOrderList.sort() + + for groupOrder in groupOrderList: + items = serverPartitionDict[groupOrder].items() + items.sort(key = itemgetter(0)) + serverList = serverList + [ items[i][1] for i in range(0,len(items))] + return serverList + + +def clearExportDirectory(serverList, volumeName, volumeUuid): + thisServerName = getCurrentServerName() + for exportServer in serverList: + serverName, partition = exportServer[0].split(":") + if thisServerName != serverName: + continue + partitionUuid = getUuidByDiskPartition(getDevice(partition)) + if not partitionUuid: + log("unable to find uuid of partition %s" % partition) + return False + volumeDirName = "%s/%s/%s" % (Globals.GLUSTER_LUN_DIR, partitionUuid, volumeUuid) + if os.path.exists(volumeDirName): + ## Removing /data/PARTITION-UUID/VOLUME-UUID/ + ## TODO: Get an option to remove it at this time + if runCommandFG("mv -f %s %s.delete" % (volumeDirName, volumeDirName), root=True) != 0: + return False + if runCommandFG("rm -f %s/%s/volumes/%s" % (Globals.GLUSTER_LUN_DIR, partitionUuid, volumeName), root=True) != 0: + return False + return True + + +def createExportDirectory(serverList, volumeName, volumeUuid): + thisServerName = getCurrentServerName() + tempVolumeNameFile = getTempFileName() + + try: + fp = open(tempVolumeNameFile, "w") + fp.write("VOLUME_NAME=%s\n" % volumeName) + fp.write("VOLUME_UUID=%s\n" % volumeUuid) + fp.close() + except IOError, e: + log("failed to create temporary file for volume-name: %s" % (volumeName, str(e))) + return False + + for exportServer in serverList: + serverName, partition = exportServer[0].split(":") + if thisServerName != serverName: + continue + partitionUuid = getUuidByDiskPartition(getDevice(partition)) + if not partitionUuid: + log("unable to find uuid of partition %s" % partition) + return False + + volumeDirName = "%s/%s/%s" % (Globals.GLUSTER_LUN_DIR, partitionUuid, volumeUuid) + ## Creating /data/PARTITION-UUID/VOLUME-UUID/ + if runCommandFG("mkdir %s" % volumeDirName, root=True) != 0: + return False + + ## Creating /data/PARTITION-UUID/VOLUME-UUID/exports/ + ## Creating /data/PARTITION-UUID/VOLUME-UUID/exports/brick1/ + if runCommandFG("mkdir -p %s/exports/brick1" % volumeDirName, root=True) != 0: + return False + + ## Creating /data/PARTITION-UUID/VOLUME-UUID/log/ + if runCommandFG("mkdir %s/log" % volumeDirName, root=True) != 0: + return False + + ## Creating /data/PARTITION-UUID/VOLUME-UUID/config/ + if runCommandFG("mkdir %s/config" % volumeDirName, root=True) != 0: + return False + + volumeLinkDirName = "%s/%s/volumes" % (Globals.GLUSTER_LUN_DIR, partitionUuid) + if not os.path.exists(volumeLinkDirName): + if runCommandFG("mkdir %s" % volumeLinkDirName, root=True) != 0: + return False + + ## Creating symlink + ## /data/PARTITION-UUID/volumes/VOLUME-NAME -> /data/PARTITION-UUID/VOLUME-UUID/ + command = "ln -fTs %s %s/%s" % (volumeDirName, + volumeLinkDirName, volumeName) + if runCommandFG(command, root=True) != 0: + return False + + if runCommandFG("cp -f %s %s/config/volume-name" % (tempVolumeNameFile, volumeDirName), root=True) != 0: + return False + + try: + os.remove(tempVolumeNameFile) + except OSError, e: + log("Failed to remove file %s: %s" % (tempVolumeNameFile, str(e))) + + return True + + +def getPartitionListByServerName(volumeDom, serverName, serverPartitionList=None): + partitionList = {} + if serverPartitionList: + for partitionName in serverPartitionList: + partitionUuid = getServerDiskPartitionUuid(serverName, partitionName) + if not partitionUuid: + log(syslog.LOG_ERR, "failed to get disk partition %s uuid of server %s" % (partitionName, serverName)) + return None + partitionList[partitionName] = partitionUuid + return partitionList + for group in volumeDom.getElementsByTagRoute("topology.group"): + for partitionTag in group.getElementsByTagName("partition"): + nameE = partitionTag.getElementsByTagName("name") + if not nameE: + continue + partition = XDOM.getText(nameE[0].childNodes) + if not partition: + continue + server, partitionName = partition.split(":") + if server != serverName: + continue + partitionUuid = getServerDiskPartitionUuid(serverName, partitionName) + if not partitionUuid: + log(syslog.LOG_ERR, "failed to get disk partition %s uuid of server %s" % (partitionName, serverName)) + return None + partitionList[partitionName] = partitionUuid + return partitionList + + +def isVolumeRunning(volumeName): + return Glusterd.isVolumeRunning(volumeName) + +def addVolumeMigrationDetails(sourcePartition, destinationPartition, volumeName): + migrationDom = XDOM() + if not os.path.exists(Globals.VOLUME_MIGRATION_LIST_FILE): + migrationDom.appendTagRoute("volume-migration") + else: + if not migrationDom.parseFile(Globals.VOLUME_MIGRATION_LIST_FILE): + log("Failed to load volume-migration.xml file") + return None + migrationList = migrationDom.getElementsByTagRoute("volume-migration.migration") + for tagE in migrationList: + dom = XDOM() + dom.setDomObj(tagE) + if dom.getTextByTagRoute("source-partition") == sourcePartition and \ + dom.getTextByTagRoute("destination-partition") == destinationPartition and \ + dom.getTextByTagRoute("volume-name") == volumeName: + return False + migrationTag = migrationDom.getElementsByTagRoute("volume-migration") + if not migrationTag: + return None + partitionTag = migrationDom.createTag("migration") + partitionTag.appendChild(migrationDom.createTag("source-partition", sourcePartition)) + partitionTag.appendChild(migrationDom.createTag("destination-partition", destinationPartition)) + partitionTag.appendChild(migrationDom.createTag("volume-name", volumeName)) + migrationTag[0].appendChild(partitionTag) + if not migrationDom.writexml(Globals.VOLUME_MIGRATION_LIST_FILE): + log("Unable to write disk migration details into %s/volume-migration.xml" % Globals.GLUSTER_BASE_DIR) + return False + return True + + +def removeVolumeMigrationDetails(sourcePartition, destinationPartition, volumeName): + migrationDom = XDOM() + if not os.path.exists(Globals.VOLUME_MIGRATION_LIST_FILE): + return None + if not migrationDom.parseFile(Globals.VOLUME_MIGRATION_LIST_FILE): + log("Failed to load volume-migration.xml file") + return None + migrationList = migrationDom.getElementsByTagRoute("volume-migration.migration") + for tagE in migrationList: + dom = XDOM() + dom.setDomObj(tagE) + if dom.getTextByTagRoute("source-partition") == sourcePartition and \ + dom.getTextByTagRoute("destination-partition") == destinationPartition and \ + dom.getTextByTagRoute("volume-name") == volumeName: + migrationDom.getElementsByTagRoute("volume-migration")[0].removeChild(tagE) + if not migrationDom.writexml(Globals.VOLUME_MIGRATION_LIST_FILE): + log("Unable to write disk migration details into %s/volume-migration.xml" % Globals.GLUSTER_BASE_DIR) + return False + return True + + +def addPartitionMigrationDetails(sourcePartition, destinationPartition, volumeList=None): + migrationDom = XDOM() + if not os.path.exists(Globals.MIGRATE_PARTITION_LIST_FILE): + migrationDom.appendTagRoute("partition-migration") + else: + if not migrationDom.parseFile(Globals.MIGRATE_PARTITION_LIST_FILE): + log("Failed to load migration.xml file") + return None + migrationList = migrationDom.getElementsByTagRoute("partition-migration.migration") + for tagE in migrationList: + dom = XDOM() + dom.setDomObj(tagE) + if dom.getTextByTagRoute("source-partition") == sourcePartition: + return False + if dom.getTextByTagRoute("destination-partition") == destinationPartition: + return False + migrationTag = migrationDom.getElementsByTagRoute("partition-migration") + if not migrationTag: + return None + partitionTag = migrationDom.createTag("migration") + partitionTag.appendChild(migrationDom.createTag("source-partition", sourcePartition)) + partitionTag.appendChild(migrationDom.createTag("destination-partition", destinationPartition)) + migrationTag[0].appendChild(partitionTag) + if not migrationDom.writexml(Globals.MIGRATE_PARTITION_LIST_FILE): + log("Unable to write disk migration details into %s/migration.xml" % Globals.GLUSTER_BASE_DIR) + return False + if volumeList: + for volumeName in volumeList: + addVolumeMigrationDetails(sourcePartition, destinationPartition, volumeName) + return True + + +def removePartitionMigrationDetails(sourcePartition, destinationPartition, volumeList=None): + migrationDom = XDOM() + if not os.path.exists(Globals.MIGRATE_PARTITION_LIST_FILE): + return None + if not migrationDom.parseFile(Globals.MIGRATE_PARTITION_LIST_FILE): + log("Failed to load migration.xml file") + return None + migrationList = migrationDom.getElementsByTagRoute("partition-migration.migration") + for tagE in migrationList: + dom = XDOM() + dom.setDomObj(tagE) + if dom.getTextByTagRoute("source-partition") == sourcePartition and \ + dom.getTextByTagRoute("destination-partition") == destinationPartition: + migrationDom.getElementsByTagRoute("partition-migration")[0].removeChild(tagE) + if not migrationDom.writexml(Globals.MIGRATE_PARTITION_LIST_FILE): + log("Unable to write disk migration details into %s/migration.xml" % Globals.GLUSTER_BASE_DIR) + return False + if volumeList: + for volumeName in volumeList: + removeVolumeMigrationDetails(sourcePartition, destinationPartition, volumeName) + return True + + +def isMigrationInProgress(partition): + migrationDom = XDOM() + if not os.path.exists(Globals.MIGRATE_PARTITION_LIST_FILE): + return None + if not migrationDom.parseFile(Globals.MIGRATE_PARTITION_LIST_FILE): + log("Failed to load migration.xml file") + return None + migrationList = migrationDom.getElementsByTagRoute("partition-migration.migration") + for tagE in migrationList: + dom = XDOM() + dom.setDomObj(tagE) + if migrationDom.getTextByTagRoute("source-partition") == partition or \ + migrationDom.getTextByTagRoute("destination-partition") == partition: + return True + return False + + +def getServerDiskPartitionUuid(serverName, partition): + diskConfigDom = XDOM() + if not diskConfigDom.parseFile("%s/%s/disk.xml" % (Globals.SERVER_CONF_DIR, serverName)): + return None + for disk in diskConfigDom.getElementsByTagRoute("disks.disk"): + diskDom = XDOM() + diskDom.setDomObj(disk) + partitionList = diskDom.getElementsByTagRoute("partition") + for tagE in partitionList: + partitionDom = XDOM() + partitionDom.setDomObj(tagE) + if partitionDom.getTextByTagRoute("device") == partition: + return partitionDom.getTextByTagRoute("uuid") + + +def getVolumeServerList(requestDom, requestFlag=True): + if requestFlag: + serverGroupElementList = requestDom.getElementsByTagRoute("command.volume.topology.group") + else: + serverGroupElementList = requestDom.getElementsByTagRoute("volume.topology.group") + if not serverGroupElementList: + return None + serverList = [] + partitionDom = XDOM() + for group in serverGroupElementList: + for partition in group.getElementsByTagName("partition"): + partitionDom.setDomObj(partition) + partitionName = partitionDom.getTextByTagRoute("name") + if not partitionName: + continue + serverPartition = partitionName.split(":") + if not(len(serverPartition) > 1 and serverPartition[1]): + return None + if serverPartition[0] not in serverList: + serverList.append(serverPartition[0]) + return serverList + + +def getVolumeServerListByName(volumeName): + serverList = [] + serverDom = XDOM() + volumeDom = XDOM() + if not os.path.exists("%s/%s.xml" % (Globals.VOLUME_CONF_DIR, volumeName)): + return False + if not volumeDom.parseFile("%s/%s.xml" % (Globals.VOLUME_CONF_DIR, volumeName)): + return False + return getVolumeServerList(volumeDom, False) + + +def getMigrateVolumeServerPartitionInfo(volumeName): + volumeMigrationDom = XDOM() + if not volumeMigrationDom.parseFile(Globals.VOLUME_MIGRATION_LIST_FILE): + Utils.log("Failed to parse file %s" % Globals.VOLUME_MIGRATION_LIST_FILE) + return None + volumeInfo = {} + dom = XDOM() + for tagE in volumeMigrationDom.getElementsByTagRoute("volume-migration.migration"): + dom.setDomObj(tagE) + if dom.getTextByTagRoute("volume-name") == volumeName: + volumeInfo['Name'] = volumeName + volumeInfo['SourcePartition'] = dom.getTextByTagRoute("source-partition") + volumeInfo['DestinationPartition'] = dom.getTextByTagRoute("destination-partition") + return volumeInfo + return None diff --git a/src/com.gluster.storage.management.server.scripts/src/nodes/XmlHandler.py b/src/com.gluster.storage.management.server.scripts/src/nodes/XmlHandler.py index d5a1fe19..72164ffb 100644 --- a/src/com.gluster.storage.management.server.scripts/src/nodes/XmlHandler.py +++ b/src/com.gluster.storage.management.server.scripts/src/nodes/XmlHandler.py @@ -343,4 +343,4 @@ def test(): networkInterfaces.appendChild(networkTag)
print rs.toprettyxml()
-test()
+#test()
diff --git a/src/com.gluster.storage.management.server.scripts/src/nodes/clear_volume_directory.py b/src/com.gluster.storage.management.server.scripts/src/nodes/clear_volume_directory.py new file mode 100755 index 00000000..3bd0ab6f --- /dev/null +++ b/src/com.gluster.storage.management.server.scripts/src/nodes/clear_volume_directory.py @@ -0,0 +1,101 @@ +#!/usr/bin/python +# Copyright (C) 2010 Gluster, Inc. <http://www.gluster.com> +# This file is part of Gluster Storage Platform. +# +# Gluster Storage Platform 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 Storage Platform 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/>. +import os +import sys +import syslog +import time +from XmlHandler import ResponseXml +import DiskUtils +import Utils +import Common +from optparse import OptionParser + +def clearVolumeDirectory(disk, volumeName, todelete): + + # Retrieving disk uuid + diskUuid = DiskUtils.getUuidByDiskPartition(DiskUtils.getDevice(disk)) + + rs = ResponseXml() + if not diskUuid: + Common.log(syslog.LOG_ERR, "failed to find disk:%s uuid" % disk) + rs.appendTagRoute("status.code", "-1") + rs.appendTagRoute("status.message", "Error: Unable to find disk uuid") + return rs.toprettyxml() + + # Retrieving disk mount point using disk uuid + diskMountPoint = DiskUtils.getMountPointByUuid(diskUuid) + if not os.path.exists(diskMountPoint): + Common.log(syslog.LOG_ERR, "failed to retrieve disk:%s mount point" % disk) + rs.appendTagRoute("status.code", "-1") + rs.appendTagRoute("status.message", "Error: Failed to retrieve disk details") + return rs.toprettyxml() + + # clear volume directory from the disk + volumeDirectory = "%s/%s" % (diskMountPoint, volumeName) + newVolumeDirectoryName = "%s_%s" % (volumeDirectory, time.time()) + command = ["sudo", "mv", "-f", volumeDirectory, newVolumeDirectoryName] + rv = Utils.runCommandFG(command, stdout=True, root=True) + message = Common.stripEmptyLines(rv["Stdout"]) + if rv["Stderr"]: + error = Common.stripEmptyLines(rv["Stderr"]) + message += "Error: [%s]" % (error) + Common.log(syslog.LOG_ERR, "failed to rename volume directory %s, %s" % (volumeDirectory, error)) + rs.appendTagRoute("status.code", rv["Status"]) + rs.appendTagRoute("status.message", message) + return rs.toprettyxml() + + if not todelete: + rv["Status"] = "0" + rs.appendTagRoute("status.code", rv["Status"]) + rs.appendTagRoute("status.message", message) + return rs.toprettyxml() + + command = ["sudo", "rm", "-fr", newVolumeDirectoryName] + rv = Utils.runCommandFG(command, stdout=True, root=True) + message = Common.stripEmptyLines(rv["Stdout"]) + if rv["Stderr"]: + error = Common.stripEmptyLines(rv["Stderr"]) + message += "Error: [%s]" % (error) + Common.log(syslog.LOG_ERR, "failed to clear volume directory %s, %s" % (newVolumeDirectoryName, error)) + rs.appendTagRoute("status.code", rv["Status"]) + rs.appendTagRoute("status.message", message) + return rs.toprettyxml() + + if not rv["Status"]: + rv["Status"] = "0" + rs.appendTagRoute("status.code", rv["Status"]) + rs.appendTagRoute("status.message", message) + return rs.toprettyxml() + +def main(): + parser = OptionParser() + parser.add_option("-d", "--delete", dest="deletedir", action="store_true", default=False, help="force delete") + (options, args) = parser.parse_args() + + if len(args) != 2: + print >> sys.stderr, "usage: %s <disk name> <volume name> [-d/--delete]" % sys.argv[0] + sys.exit(-1) + + disk = args[0] + volumeName = args[1] + print clearVolumeDirectory(disk, volumeName, options.deletedir) + sys.exit(0) + +if __name__ == "__main__": + main() + diff --git a/src/com.gluster.storage.management.server.scripts/src/nodes/create_volume_directory.py b/src/com.gluster.storage.management.server.scripts/src/nodes/create_volume_directory.py new file mode 100755 index 00000000..b8fb2166 --- /dev/null +++ b/src/com.gluster.storage.management.server.scripts/src/nodes/create_volume_directory.py @@ -0,0 +1,85 @@ +#!/usr/bin/python +# Copyright (C) 2010 Gluster, Inc. <http://www.gluster.com> +# This file is part of Gluster Storage Platform. +# +# Gluster Storage Platform 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 Storage Platform 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/>. +import os +import sys +import syslog +from XmlHandler import ResponseXml +import DiskUtils +import Utils +import Common + +def createDirectory(disk, volumeName): + + # Retrieving disk uuid + diskUuid = DiskUtils.getUuidByDiskPartition(DiskUtils.getDevice(disk)) + + rs = ResponseXml() + if not diskUuid: + Common.log(syslog.LOG_ERR, "failed to find disk:%s uuid" % disk) + rs.appendTagRoute("status.code", "-1") + rs.appendTagRoute("status.message", "Error: Unable to find disk uuid") + return rs.toprettyxml() + + # Retrieving disk mount point using disk uuid + diskMountPoint = DiskUtils.getMountPointByUuid(diskUuid) + if not os.path.exists(diskMountPoint): + Common.log(syslog.LOG_ERR, "failed to retrieve disk:%s mount point" % disk) + rs.appendTagRoute("status.code", "-1") + rs.appendTagRoute("status.message", "Error: Failed to retrieve disk details") + return rs.toprettyxml() + + # creating volume directory under disk mount point + volumeDirectory = "%s/%s" % (diskMountPoint, volumeName) + if os.path.exists(volumeDirectory): + Common.log(syslog.LOG_ERR, "Volume directory:%s already exists" % (volumeDirectory)) + rs.appendTagRoute("status.code", "-2") + rs.appendTagRoute("status.message", "Volume directory already exists!") + return rs.toprettyxml() + + if not os.path.exists(volumeDirectory): + command = ["sudo", "mkdir", volumeDirectory] + rv = Utils.runCommandFG(command, stdout=True, root=True) + message = Common.stripEmptyLines(rv["Stdout"]) + if rv["Stderr"]: + error = Common.stripEmptyLines(rv["Stderr"]) + message += "Error: [%s]" % (error) + Common.log(syslog.LOG_ERR, "failed to create volume directory %s, %s" % (volumeDirectory, error)) + rs.appendTagRoute("status.code", rv["Status"]) + rs.appendTagRoute("status.message", message) + return rs.toprettyxml() + + if not rv["Status"]: + rv["Status"] = "0" + if rv["Status"] == "0": + message = volumeDirectory + rs.appendTagRoute("status.code", rv["Status"]) + rs.appendTagRoute("status.message", message) + return rs.toprettyxml() + +def main(): + if len(sys.argv) != 3: + print >> sys.stderr, "usage: %s <disk name> <volume name>" % sys.argv[0] + sys.exit(-1) + + disk = sys.argv[1] + volumeName = sys.argv[2] + print createDirectory(disk, volumeName) + sys.exit(0) + +if __name__ == "__main__": + main() diff --git a/src/com.gluster.storage.management.server.scripts/src/nodes/get_disk_mount_point.py b/src/com.gluster.storage.management.server.scripts/src/nodes/get_disk_mount_point.py new file mode 100755 index 00000000..b2274b4d --- /dev/null +++ b/src/com.gluster.storage.management.server.scripts/src/nodes/get_disk_mount_point.py @@ -0,0 +1,64 @@ +#!/usr/bin/python +# Copyright (C) 2010 Gluster, Inc. <http://www.gluster.com> +# This file is part of Gluster Storage Platform. +# +# Gluster Storage Platform 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 Storage Platform 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/>. + +import os +import syslog +import Common +from DiskUtils import * +from XmlHandler import ResponseXml + + +def getmountpoint(path): + if not path: + Common.log(syslog.LOG_ERR, "Not a valid path:%s" % path) + rs.appendTagRoute("status.code", "-1") + rs.appendTagRoute("status.message", "Error: given path name is empty") + return rs.toprettyxml() + + rs = ResponseXml() + mountPoint = None + + for line in readFsTab(): + if path.startswith(line['MountPoint']): + if not mountPoint: + mountPoint = line['MountPoint'] + if len(line['MountPoint']) > len(mountPoint): + mountPoint = line['MountPoint'] + + if "/" == mountPoint or not mountPoint: + Common.log(syslog.LOG_ERR, "failed to find mount point of the given path:%s" % path) + rs.appendTagRoute("status.code", "-1") + rs.appendTagRoute("status.message", "Error: Unable to find disk mount point") + return rs.toprettyxml() + + rs.appendTagRoute("status.code", "0") + rs.appendTagRoute("status.message", mountPoint) + return rs.toprettyxml() + +def main(): + if len(sys.argv) != 2: + print >> sys.stderr, "usage: %s <path>" % sys.argv[0] + sys.exit(-1) + + path = sys.argv[1] + print getmountpoint(path) + sys.exit(0) + +if __name__ == "__main__": + main() + diff --git a/src/com.gluster.storage.management.server.scripts/src/nodes/get_disk_name_by_path.py b/src/com.gluster.storage.management.server.scripts/src/nodes/get_disk_name_by_path.py new file mode 100755 index 00000000..72eb80dd --- /dev/null +++ b/src/com.gluster.storage.management.server.scripts/src/nodes/get_disk_name_by_path.py @@ -0,0 +1,69 @@ +#!/usr/bin/python +# Copyright (C) 2010 Gluster, Inc. <http://www.gluster.com> +# This file is part of Gluster Storage Platform. +# +# Gluster Storage Platform 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 Storage Platform 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/>. + +import os +import syslog +import Common +from DiskUtils import * +from XmlHandler import ResponseXml + + +def getmountpoint(path): + if not path: + Common.log(syslog.LOG_ERR, "Not a valid path:%s" % path) + rs.appendTagRoute("status.code", "-1") + rs.appendTagRoute("status.message", "Error: given path name is empty") + return rs.toprettyxml() + + rs = ResponseXml() + mountPoint = None + fsTabEntry = None + for line in readFsTab(): + if path.startswith(line['MountPoint']): + if not mountPoint: + mountPoint = line['MountPoint'] + fsTabEntry = line + if len(line['MountPoint']) > len(mountPoint): + mountPoint = line['MountPoint'] + fsTabEntry = line + + if "/" == mountPoint or not mountPoint: + Common.log(syslog.LOG_ERR, "failed to find mount point of the given path:%s" % path) + rs.appendTagRoute("status.code", "-1") + rs.appendTagRoute("status.message", "Error: Unable to find disk mount point") + return rs.toprettyxml() + + rs.appendTagRoute("status.code", "0") + if fsTabEntry["Device"].startswith("UUID="): + rs.appendTagRoute("status.message", getDiskPartitionByUuid(fsTabEntry["Device"].split("UUID=")[-1])) + else: + rs.appendTagRoute("status.message", "Unable to find disk name") + return rs.toprettyxml() + +def main(): + if len(sys.argv) != 2: + print >> sys.stderr, "usage: %s <path>" % sys.argv[0] + sys.exit(-1) + + path = sys.argv[1] + print getmountpoint(path) + sys.exit(0) + +if __name__ == "__main__": + main() + diff --git a/src/com.gluster.storage.management.server.scripts/src/nodes/get_volume_brick_log.py b/src/com.gluster.storage.management.server.scripts/src/nodes/get_volume_brick_log.py new file mode 100755 index 00000000..7c912412 --- /dev/null +++ b/src/com.gluster.storage.management.server.scripts/src/nodes/get_volume_brick_log.py @@ -0,0 +1,109 @@ +#!/usr/bin/python +# Copyright (C) 2009,2010 Gluster, Inc. <http://www.gluster.com> +# This file is part of Gluster Storage Platform. +# +# Gluster Storage Platform 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 Storage Platform 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/>. + +import re +import os +import sys +from XmlHandler import ResponseXml + +def enumLogType(logCode): + if "M" == logCode.upper(): + return "EMERGENCY" + elif "A" == logCode.upper(): + return "ALERT" + elif "C" == logCode.upper(): + return "CRITICAL" + elif "E" == logCode.upper(): + return "ERROR" + elif "W" == logCode.upper(): + return "WARNING" + elif "N" == logCode.upper(): + return "NOTICE" + elif "I" == logCode.upper(): + return "INFO" + elif "D" == logCode.upper(): + return "DEBUG" + elif "T" == logCode.upper(): + return "TRACE" + else: + return "UNKNOWN" +##--end of enumLogType() + +def addLog(responseDom, logMessageTag, loginfo): + logTag = responseDom.createTag("logMessage", None) + logTag.appendChild(responseDom.createTag("timestamp", loginfo[0] + " " + loginfo[1])) + logTag.appendChild(responseDom.createTag("severity", enumLogType(loginfo[2]))) + logTag.appendChild(responseDom.createTag("message", loginfo[3])) + logMessageTag.appendChild(logTag) + return True +##--end of addLog() + +def logSplit(log): + loginfo = log.strip().split(None, 3) + loginfo[0] = loginfo[0][1:] #-- Remove '[' + loginfo[1] = loginfo[1][0:-1] #-- Remove ']' + return loginfo +##--end of logSplit() + +def getVolumeLog(logFilePath, tailCount): + rs = ResponseXml() + if not logFilePath: + rs.appendTagRoute("status.code", "-1") + rs.appendTagRoute("status.message", "No log file path given") + return rs.toprettyxml() + + if not tailCount: + rs.appendTagRoute("status.code", "-1") + rs.appendTagRoute("status.message", "No tail count given") + return rs.toprettyxml() + + pattern = '\[\d{4}-\d{2}-\d{2}\s{1}\d{2}:\d{2}:\d{2}.\d+\]\s{1}([MACEWNIDT]){1}\s+' + logMessagesTag = rs.createTag("logMessages") + if not os.path.exists(logFilePath): + rs.appendTagRoute("status.code", "-1") + rs.appendTagRoute("status.message", "volume log file [%s] not found!" % logFilePath) + return rs.toprettyxml + + fp = open(logFilePath) + #lines = [line for line in fp] + lines = [line for line in fp if re.match(pattern, line)] + fp.close() + i = len(lines) - int(tailCount) + if i < 0: + i = 0 + for log in lines[i:]: + loginfo = logSplit(log) + addLog(rs, logMessagesTag, loginfo) + rs.appendTagRoute("status.code", "0") + rs.appendTagRoute("status.message", "Success") + rs.appendTag(logMessagesTag) + return rs.toprettyxml() +##--end of getVolumeLog() + +def main(): + if len(sys.argv) != 3: + print >> sys.stderr, "usage: %s <Log File Path> <Line Count>" % sys.argv[0] + sys.exit(-1) + + logFilePath = sys.argv[1] + tailCount = sys.argv[2] + print getVolumeLog(logFilePath, tailCount) + sys.exit(0) + +if __name__ == "__main__": + main() diff --git a/src/com.gluster.storage.management.server.scripts/src/nodes/get_volume_log.py b/src/com.gluster.storage.management.server.scripts/src/nodes/get_volume_log.py new file mode 100755 index 00000000..826ade6e --- /dev/null +++ b/src/com.gluster.storage.management.server.scripts/src/nodes/get_volume_log.py @@ -0,0 +1,132 @@ +# Copyright (C) 2009,2010 Gluster, Inc. <http://www.gluster.com> +# This file is part of Gluster Storage Platform. +# +# Gluster Storage Platform 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 Storage Platform 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/>. + +import Globals +import syslog +import Commands +import Utils +from VolumeUtils import * +from XmlHandler import ResponseXml + + +def enumLogType(logCode): + if "M" == logCode.upper(): + return "EMERGENCY" + elif "A" == logCode.upper(): + return "ALERT" + elif "C" == logCode.upper(): + return "CRITICAL" + elif "E" == logCode.upper(): + return "ERROR" + elif "W" == logCode.upper(): + return "WARNING" + elif "N" == logCode.upper(): + return "NOTICE" + elif "I" == logCode.upper(): + return "INFO" + elif "D" == logCode.upper(): + return "DEBUG" + elif "T" == logCode.upper(): + return "TRACE" + else: + return "UNKNOWN" +##--end of enumLogType() + + +def addLog(responseDom, logMessageTag, loginfo): + logTag = responseDom.createTag("log", None) + logTag.appendChild(responseDom.createTag("date", loginfo[0])) + logTag.appendChild(responseDom.createTag("time", loginfo[1])) + logTag.appendChild(responseDom.createTag("type", enumLogType(loginfo[2]))) + logTag.appendChild(responseDom.createTag("message", loginfo[3])) + logMessageTag.appendChild(logTag) + return True +##--end of addLog() + + +def logSplit(log): + loginfo = log.strip().split(None, 3) + loginfo[0] = loginfo[0][1:] #-- Remove '[' + loginfo[1] = loginfo[1][0:-1] #-- Remove ']' + return loginfo +##--end of logSplit() + + +def getVolumeLog(volumeName, tailCount): + rs = ResponseXml() + if not volumeName: + rs.appendTagRoute("status.code", "-1") + rs.appendTagRoute("status.message", "No volume name given") + return rs.toprettyxml() + + if not tailCount: + rs.appendTagRoute("status.code", "-1") + rs.appendTagRoute("status.message", "No tail count given") + return rs.toprettyxml() + + thisServerName = getCurrentServerName() + if not thisServerName: + rs.appendTagRoute("status.code", "-2") + rs.appendTagRoute("status.message", "Failed to get current server name") + return rs.toprettyxml() + + volumeDom = XDOM() + partitionList = getPartitionListByServerName(volumeDom, thisServerName) + if not partitionList: + rs.appendTagRoute("status.code", "-3") + rs.appendTagRoute("status.message", "Failed to get server partition details") + return rs.toprettyxml() + + pattern = '\[\d{4}-\d{2}-\d{2}\s{1}\d{2}:\d{2}:\d{2}.\d+\]\s{1}([MACEWNIDT]){1}\s+' + logMessagesTag = rs.createTag("response.logMessages") + for partitionName in partitionList: + logMessageTag = rs.createTag("logMessage") + logMessageTag.appendChild("disk", "%s:%s" % (thisServerName, partitionName)) + + logDirectory = "%s/%s/%s/log" % (Globals.GLUSTER_LUN_DIR, partitionList[partitionName], volumeUuid) + logFileName = "%s/%s-%s-%s-exports-brick1.log" % (logDirectory, + Globals.GLUSTER_LUN_DIR[1:], + partitionList[partitionName], + volumeUuid) + if not os.path.exists(logFileName): + Utils.log("volume log file not found %s" % logFileName) + continue + fp = open(logFileName) + lines = [line for line in fp if re.match(pattern, line)] + fp.close() + i = len(lines) - int(tailCount) + if i < 0: + i = 0 + for log in lines[i:]: + loginfo = logSplit(log) + addLog(rs, logMessageTag, loginfo) + logMessagesTag.appendChild(logMessageTag) + return rs.toprettyxml() +##--end of getVolumeLog() + +def main(): + if len(sys.argv) != 3: + print >> sys.stderr, "usage: %s <disk name> <volume name>" % sys.argv[0] + sys.exit(-1) + + volumeName = sys.argv[1] + tailCount = sys.argv[2] + print getVolumeLog(volumeName, tailCount) + sys.exit(0) + +if __name__ == "__main__": + main() diff --git a/src/com.gluster.storage.management.server.scripts/src/nodes/multicast_response.py b/src/com.gluster.storage.management.server.scripts/src/nodes/multicast_response.py index 64bc0899..dba65c07 100644 --- a/src/com.gluster.storage.management.server.scripts/src/nodes/multicast_response.py +++ b/src/com.gluster.storage.management.server.scripts/src/nodes/multicast_response.py @@ -16,10 +16,26 @@ # along with this program. If not, see
# <http://www.gnu.org/licenses/>.
+import os
+import string
+import time
+import Utils
import socket
import struct
import Globals
+def isinpeer():
+ command = "gluster peer status"
+ status = Utils.runCommand(command, output=True, root=True)
+ if status["Status"] == 0:
+ return True
+ #lines = status["Stdout"].split("\n")
+ #for line in lines:
+ # if string.upper(line).startswith("HOSTNAME: %s" % string.upper(socket.gethostname)):
+ # return True
+ Utils.log("command [%s] failed with [%d:%s]" % (command, status["Status"], os.strerror(status["Status"])))
+ return False
+
def response(multiCastGroup, port):
# waiting for the request!
socketRequest = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
@@ -33,6 +49,9 @@ def response(multiCastGroup, port): #TODO: Remove infinite loop and make this as a deamon (service)
while True:
+ if isinpeer():
+ time.sleep(5)
+ continue
request = socketRequest.recvfrom(1024)
if request and request[0].upper() == "SERVERDISCOVERY":
socketSend.sendto(socket.gethostname(), (multiCastGroup, port))
diff --git a/src/com.gluster.storage.management.server/WebContent/WEB-INF/web.xml b/src/com.gluster.storage.management.server/WebContent/WEB-INF/web.xml index 34337a5d..6d7d1406 100644 --- a/src/com.gluster.storage.management.server/WebContent/WEB-INF/web.xml +++ b/src/com.gluster.storage.management.server/WebContent/WEB-INF/web.xml @@ -37,6 +37,10 @@ <param-name>com.sun.jersey.config.property.packages</param-name> <param-value>com.gluster.storage.management.server.resources</param-value> </init-param> + <init-param> + <param-name>com.sun.jersey.spi.container.ResourceFilters</param-name> + <param-value>com.gluster.storage.management.server.filters.GlusterResourceFilterFactory</param-value> + </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> diff --git a/src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/constants/VolumeOptionsDefaults.java b/src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/constants/VolumeOptionsDefaults.java index 4093a4ee..5c9a6505 100644 --- a/src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/constants/VolumeOptionsDefaults.java +++ b/src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/constants/VolumeOptionsDefaults.java @@ -100,9 +100,9 @@ public class VolumeOptionsDefaults { volumeOptionsInfo.add(new VolumeOptionInfo("diagnostics.dump-fd-stats", "Statistics related to file-operations would be tracked inside GlusterFS data-structures.", "off")); volumeOptionsInfo.add(new VolumeOptionInfo("diagnostics.brick-log-level", - "Changes the log-level of the bricks (servers).", "NORMAL")); + "Changes the log-level of the bricks (servers).", "INFO")); volumeOptionsInfo.add(new VolumeOptionInfo("diagnostics.client-log-level", - "Changes the log-level of the clients.", "NORMAL")); + "Changes the log-level of the clients.", "INFO")); volumeOptionsInfo.add(new VolumeOptionInfo("nfs.enable-ino32", "Use this option from the CLI to make Gluster NFS return 32-bit inode numbers instead of 64-bit.", "off")); diff --git a/src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/filters/AuditFilter.java b/src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/filters/AuditFilter.java new file mode 100644 index 00000000..b23d9c4f --- /dev/null +++ b/src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/filters/AuditFilter.java @@ -0,0 +1,31 @@ +/** + * + */ +package com.gluster.storage.management.server.filters; + +import com.sun.jersey.spi.container.ContainerRequest; +import com.sun.jersey.spi.container.ContainerRequestFilter; +import com.sun.jersey.spi.container.ContainerResponseFilter; +import com.sun.jersey.spi.container.ResourceFilter; + +/** + * Resource filter for maintaining audit trail of resource access + */ +public class AuditFilter implements ResourceFilter, ContainerRequestFilter { + + @Override + public ContainerRequestFilter getRequestFilter() { + return this; + } + + @Override + public ContainerResponseFilter getResponseFilter() { + return null; + } + + @Override + public ContainerRequest filter(ContainerRequest req) { + System.out.println("Resource access [" + req.getMethod() + "][" + req.getPath() + "]"); + return req; + } +} diff --git a/src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/filters/GlusterResourceFilterFactory.java b/src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/filters/GlusterResourceFilterFactory.java new file mode 100644 index 00000000..899ba16e --- /dev/null +++ b/src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/filters/GlusterResourceFilterFactory.java @@ -0,0 +1,31 @@ +/** + * + */ +package com.gluster.storage.management.server.filters; + +import java.util.ArrayList; +import java.util.List; + +import com.sun.jersey.api.model.AbstractMethod; +import com.sun.jersey.spi.container.ResourceFilter; +import com.sun.jersey.spi.container.ResourceFilterFactory; + +/** + * Gluster resource filter factory. As of now, this creates only one filter - the audit filter {@code AuditFilter} + */ +public class GlusterResourceFilterFactory implements ResourceFilterFactory { + + public GlusterResourceFilterFactory() { + } + + /* (non-Javadoc) + * @see com.sun.jersey.spi.container.ResourceFilterFactory#create(com.sun.jersey.api.model.AbstractMethod) + */ + @Override + public List<ResourceFilter> create(AbstractMethod arg0) { + List<ResourceFilter> filters = new ArrayList<ResourceFilter>(); + filters.add(new AuditFilter()); + + return filters; + } +} diff --git a/src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/resources/AlertsResource.java b/src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/resources/AlertsResource.java index c4948596..e33e06c9 100644 --- a/src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/resources/AlertsResource.java +++ b/src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/resources/AlertsResource.java @@ -53,7 +53,7 @@ public class AlertsResource { // Alert #4 alert = new Alert(); alert.setId("0004"); - alert.setReference("Volume2:server2:sda1"); // volume:[Disk name] + alert.setReference("Volume3:server2:sda1"); // volume:[Disk name] alert.setType(Alert.ALERT_TYPES.OFFLINE_VOLUME_DISKS_ALERT); alert.setMessage(alert.getAlertType(alert.getType()) + " in volume [" + alert.getReference().split(":")[0] + "] disk [" + alert.getReference().split(":")[1] + ":" + alert.getReference().split(":")[2] + "]"); diff --git a/src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/resources/GlusterServersResource.java b/src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/resources/GlusterServersResource.java index 42f7760e..c1f0435c 100644 --- a/src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/resources/GlusterServersResource.java +++ b/src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/resources/GlusterServersResource.java @@ -37,8 +37,8 @@ import com.gluster.storage.management.core.model.Status; import com.gluster.storage.management.core.response.GenericResponse; import com.gluster.storage.management.core.response.GlusterServerListResponse; import com.gluster.storage.management.core.response.GlusterServerResponse; -import com.gluster.storage.management.core.utils.GlusterUtil; import com.gluster.storage.management.core.utils.ProcessResult; +import com.gluster.storage.management.server.utils.GlusterUtil; import com.sun.jersey.spi.resource.Singleton; @Component diff --git a/src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/resources/VolumesResource.java b/src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/resources/VolumesResource.java index 49fb1e0d..586bf5a3 100644 --- a/src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/resources/VolumesResource.java +++ b/src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/resources/VolumesResource.java @@ -24,14 +24,29 @@ import static com.gluster.storage.management.core.constants.RESTConstants.FORM_P import static com.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_VALUE_START; import static com.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_VALUE_STOP; import static com.gluster.storage.management.core.constants.RESTConstants.PATH_PARAM_VOLUME_NAME; +import static com.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_DELETE_OPTION; +import static com.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_DISKS; +import static com.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_DISK_NAME; +import static com.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_FROM_TIMESTAMP; +import static com.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_LINE_COUNT; +import static com.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_LOG_SEVERITY; +import static com.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_TO_TIMESTAMP; +import static com.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_VOLUME_NAME; import static com.gluster.storage.management.core.constants.RESTConstants.RESOURCE_PATH_VOLUMES; import static com.gluster.storage.management.core.constants.RESTConstants.SUBRESOURCE_DEFAULT_OPTIONS; +import static com.gluster.storage.management.core.constants.RESTConstants.SUBRESOURCE_DISKS; +import static com.gluster.storage.management.core.constants.RESTConstants.SUBRESOURCE_LOGS; import static com.gluster.storage.management.core.constants.RESTConstants.SUBRESOURCE_OPTIONS; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; import java.util.List; import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.POST; @@ -39,24 +54,35 @@ 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 com.gluster.storage.management.core.constants.CoreConstants; import com.gluster.storage.management.core.constants.RESTConstants; +import com.gluster.storage.management.core.exceptions.GlusterRuntimeException; +import com.gluster.storage.management.core.model.LogMessage; import com.gluster.storage.management.core.model.Status; import com.gluster.storage.management.core.model.Volume; +import com.gluster.storage.management.core.model.Volume.TRANSPORT_TYPE; +import com.gluster.storage.management.core.response.GenericResponse; +import com.gluster.storage.management.core.response.LogMessageListResponse; import com.gluster.storage.management.core.response.VolumeListResponse; import com.gluster.storage.management.core.response.VolumeOptionInfoListResponse; -import com.gluster.storage.management.core.utils.GlusterUtil; +import com.gluster.storage.management.core.utils.DateUtil; import com.gluster.storage.management.server.constants.VolumeOptionsDefaults; +import com.gluster.storage.management.server.utils.GlusterUtil; import com.gluster.storage.management.server.utils.ServerUtil; import com.sun.jersey.api.core.InjectParam; +import com.sun.jersey.api.representation.Form; import com.sun.jersey.spi.resource.Singleton; @Singleton @Path(RESOURCE_PATH_VOLUMES) public class VolumesResource { - private static final String SCRIPT_NAME = "preVolumeCreate.py"; - + private static final String PREPARE_BRICK_SCRIPT = "create_volume_directory.py"; + private static final String VOLUME_DIRECTORY_CLEANUP_SCRIPT = "clear_volume_directory.py"; + private static final String VOLUME_BRICK_LOG_SCRIPT = "get_volume_brick_log.py"; + @InjectParam private static ServerUtil serverUtil; private final GlusterUtil glusterUtil = new GlusterUtil(); @@ -71,6 +97,7 @@ public class VolumesResource { return new VolumeListResponse(Status.STATUS_SUCCESS, glusterUtil.getAllVolumes()); } catch (Exception e) { // TODO: log the error + e.printStackTrace(); return new VolumeListResponse(new Status(Status.STATUS_CODE_FAILURE, e.getMessage()), null); } } @@ -79,24 +106,27 @@ public class VolumesResource { @Consumes(MediaType.TEXT_XML) @Produces(MediaType.TEXT_XML) public Status createVolume(Volume volume) { - //Create the directories for the volume - List<String> bricks = new ArrayList<String>(); - for(String disk : volume.getDisks()) { - - String brickNotation = prepareBrick(volume, disk); - if (brickNotation != null) { - bricks.add(brickNotation); + // Create the directories for the volume + List<String> disks = volume.getDisks(); + Status status = createDirectories(disks, volume.getName()); + if (status.isSuccess()) { + List<String> bricks = Arrays.asList(status.getMessage().split(" ")); + status = glusterUtil.createVolume(volume, bricks); + if (status.isSuccess()) { + Status optionsStatus = glusterUtil.createOptions(volume); + if (!optionsStatus.isSuccess()) { + status.setCode(Status.STATUS_CODE_PART_SUCCESS); + status.setMessage("Error while setting volume options: " + optionsStatus); + } } else { - int failedIndex = volume.getDisks().indexOf(disk); - // TODO: Perform cleanup on all previously prepared bricks - // i.e. those disks with index < failedIndex - - return new Status(Status.STATUS_CODE_FAILURE, "Error while preparing disk [" + disk + "] for volume [" - + volume.getName() + "]"); + Status cleanupStatus = cleanupDirectories(disks, volume.getName(), disks.size(), "-d"); // delete permanently + if (!cleanupStatus.isSuccess()) { + status.setMessage(status.getMessage() + CoreConstants.NEWLINE + "Cleanup errors: " + + CoreConstants.NEWLINE + cleanupStatus); + } } } - - return glusterUtil.createVolume(volume, bricks); + return status; } @GET @@ -105,7 +135,7 @@ public class VolumesResource { public Volume getVolume(@PathParam(PATH_PARAM_VOLUME_NAME) String volumeName) { return glusterUtil.getVolume(volumeName); } - + @PUT @Path("{" + PATH_PARAM_VOLUME_NAME + "}") @Produces(MediaType.TEXT_XML) @@ -121,6 +151,42 @@ public class VolumesResource { return new Status(Status.STATUS_CODE_FAILURE, "Invalid operation code [" + operation + "]"); } + @DELETE + @Path("{" + PATH_PARAM_VOLUME_NAME + "}") + @Produces(MediaType.TEXT_XML) + public Status deleteVolume(@QueryParam(QUERY_PARAM_VOLUME_NAME) String volumeName, + @QueryParam(QUERY_PARAM_DELETE_OPTION) String deleteOption) { + Volume volume = glusterUtil.getVolume(volumeName); + Status status = glusterUtil.deleteVolume(volumeName); + + if (status.isSuccess()) { + List<String> disks = volume.getDisks(); + Status postDeleteStatus = postDelete(volumeName, disks, deleteOption); + + if (!postDeleteStatus.isSuccess()) { + status.setCode(Status.STATUS_CODE_PART_SUCCESS); + status.setMessage("Error while post deletion operation: " + postDeleteStatus); + } + } + return status; + } + + private Status postDelete(String volumeName, List<String> disks, String deleteFlag) { + String serverName, diskName, diskInfo[]; + Status result; + for (int i = 0; i < disks.size(); i++) { + diskInfo = disks.get(i).split(":"); + serverName = diskInfo[0]; + diskName = diskInfo[1]; + result = (Status) serverUtil.executeOnServer(true, serverName, VOLUME_DIRECTORY_CLEANUP_SCRIPT + " " + + diskName + " " + volumeName + " " + deleteFlag, Status.class); + if (!result.isSuccess()) { + return result; + } + } + return new Status(Status.STATUS_CODE_SUCCESS, "Post volume delete operation successfully initiated"); + } + @POST @Path("{" + PATH_PARAM_VOLUME_NAME + " }/" + SUBRESOURCE_OPTIONS) @Produces(MediaType.TEXT_XML) @@ -129,7 +195,7 @@ public class VolumesResource { @FormParam(RESTConstants.FORM_PARAM_OPTION_VALUE) String value) { return glusterUtil.setOption(volumeName, key, value); } - + @PUT @Path("{" + PATH_PARAM_VOLUME_NAME + " }/" + SUBRESOURCE_OPTIONS) @Produces(MediaType.TEXT_XML) @@ -145,25 +211,244 @@ public class VolumesResource { // whenever such a CLI command is made available in GlusterFS return new VolumeOptionInfoListResponse(Status.STATUS_SUCCESS, volumeOptionsDefaults.getDefaults()); } - - private String prepareBrick(Volume vol, String disk) { - String serverName = disk.split(":")[0]; - String diskName = disk.split(":")[1]; - Status result = (Status)serverUtil.executeOnServer(true, serverName, SCRIPT_NAME + " " + vol.getName() + " " + diskName, Status.class); + + @SuppressWarnings("rawtypes") + private Status prepareBrick(String serverName, String diskName, String volumeName) { + return (Status) ((GenericResponse) serverUtil.executeOnServer(true, serverName, PREPARE_BRICK_SCRIPT + " " + + diskName + " " + volumeName, GenericResponse.class)).getStatus(); + } + + private Status createDirectories(List<String> disks, String volumeName) { + List<String> bricks = new ArrayList<String>(); + Status status = null; + for (int i = 0; i < disks.size(); i++) { + String disk = disks.get(i); + + String[] diskParts = disk.split(":"); + String serverName = diskParts[0]; + String diskName = diskParts[1]; + try { + status = prepareBrick(serverName, diskName, volumeName); + } catch (Exception e) { + status = new Status(e); + } + if (status.isSuccess()) { + String brickDir = status.getMessage().trim(); + bricks.add(serverName + ":" + brickDir); + } else { + // Brick preparation failed. Cleanup directories already created and return failure status + Status cleanupStatus = cleanupDirectories(disks, volumeName, i + 1, "-d"); // delete permanently + if (!cleanupStatus.isSuccess()) { + // append cleanup error to prepare brick error + status.setMessage(status.getMessage() + CoreConstants.NEWLINE + cleanupStatus.getMessage()); + } + return status; + } + } + status.setMessage(bricksAsString(bricks)); + return status; + } + + //TODO Can be removed and use StringUtil.ListToString(List<String> list, String delimiter) + private String bricksAsString(List<String> bricks) { + String bricksStr = ""; + for (String brickInfo : bricks) { + bricksStr += brickInfo + " "; + } + return bricksStr.trim(); + } + + @SuppressWarnings("rawtypes") + private Status cleanupDirectories(List<String> disks, String volumeName, int maxIndex, String deleteFlag) { + String serverName, diskName, diskInfo[]; + Status result; + for (int i = 0; i < maxIndex; i++) { + diskInfo = disks.get(i).split(":"); + serverName = diskInfo[0]; + diskName = diskInfo[1]; + result = ((GenericResponse) serverUtil.executeOnServer(true, serverName, VOLUME_DIRECTORY_CLEANUP_SCRIPT + " " + + diskName + " " + volumeName + " " + deleteFlag, GenericResponse.class)).getStatus(); + if (!result.isSuccess()) { + return result; + } + } + return new Status(Status.STATUS_CODE_SUCCESS, "Directories cleaned up successfully!"); + } + + private List<LogMessage> getBrickLogs(Volume volume, String brickName, Integer lineCount) + throws GlusterRuntimeException { + // brick name format is <serverName>:<brickDirectory> + String[] brickParts = brickName.split(":"); + String serverName = brickParts[0]; + String brickDir = brickParts[1]; - if(result.isSuccess()) { - return result.getMessage(); + String logDir = glusterUtil.getLogLocation(volume.getName(), brickName); + String logFileName = glusterUtil.getLogFileNameForBrickDir(brickDir); + String logFilePath = logDir + CoreConstants.FILE_SEPARATOR + logFileName; + + // Usage: get_volume_disk_log.py <volumeName> <diskName> <lineCount> + Object responseObj = serverUtil.executeOnServer(true, serverName, VOLUME_BRICK_LOG_SCRIPT + + " " + logFilePath + " " + lineCount, LogMessageListResponse.class); + Status status = null; + LogMessageListResponse response = null; + if(responseObj instanceof LogMessageListResponse) { + response = (LogMessageListResponse)responseObj; + status = response.getStatus(); } else { - return null; + status = (Status)responseObj; + } + + if (!status.isSuccess()) { + throw new GlusterRuntimeException(status.toString()); + } + + // populate disk and trim other fields + List<LogMessage> logMessages = response.getLogMessages(); + for(LogMessage logMessage : logMessages) { + logMessage.setDisk(getDiskForBrick(volume, brickName)); + logMessage.setMessage(logMessage.getMessage().trim()); + logMessage.setSeverity(logMessage.getSeverity().trim()); } + return logMessages; + } + + @GET + @Path("{" + PATH_PARAM_VOLUME_NAME + "}/" + SUBRESOURCE_LOGS) + public LogMessageListResponse getLogs(@PathParam(PATH_PARAM_VOLUME_NAME) String volumeName, + @QueryParam(QUERY_PARAM_DISK_NAME) String diskName, @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) { + List<LogMessage> logMessages = null; + + try { + Volume volume = getVolume(volumeName); + if (diskName == null || diskName.isEmpty()) { + logMessages = getLogsForAllBricks(volume, lineCount); + } else { + // fetch logs for given brick of the volume + logMessages = getBrickLogs(volume, getBrickForDisk(volume, diskName), lineCount); + } + } catch (Exception e) { + return new LogMessageListResponse(new Status(e), null); + } + + filterLogsBySeverity(logMessages, severity); + filterLogsByTime(logMessages, fromTimestamp, toTimestamp); + return new LogMessageListResponse(Status.STATUS_SUCCESS, logMessages); + } + + private void filterLogsByTime(List<LogMessage> logMessages, String fromTimestamp, String toTimestamp) { + Date fromTime = null, toTime = null; + + if(fromTimestamp != null && !fromTimestamp.isEmpty()) { + fromTime = DateUtil.stringToDate(fromTimestamp); + } + + if(toTimestamp != null && !toTimestamp.isEmpty()) { + toTime = DateUtil.stringToDate(toTimestamp); + } + + List<LogMessage> messagesToRemove = new ArrayList<LogMessage>(); + for(LogMessage logMessage : logMessages) { + Date logTimestamp = logMessage.getTimestamp(); + if(fromTime != null && logTimestamp.before(fromTime)) { + messagesToRemove.add(logMessage); + continue; + } + + if(toTime != null && logTimestamp.after(toTime)) { + messagesToRemove.add(logMessage); + } + } + logMessages.removeAll(messagesToRemove); + } + + private void filterLogsBySeverity(List<LogMessage> logMessages, String severity) { + if(severity == null || severity.isEmpty()) { + return; + } + + List<LogMessage> messagesToRemove = new ArrayList<LogMessage>(); + for(LogMessage logMessage : logMessages) { + if(!logMessage.getSeverity().equals(severity)) { + messagesToRemove.add(logMessage); + } + } + logMessages.removeAll(messagesToRemove); + } + + private List<LogMessage> getLogsForAllBricks(Volume volume, Integer lineCount) { + List<LogMessage> logMessages; + logMessages = new ArrayList<LogMessage>(); + // fetch logs for every brick of the volume + for (String brick : volume.getBricks()) { + logMessages.addAll(getBrickLogs(volume, brick, lineCount)); + } + + // Sort the log messages based on log timestamp + Collections.sort(logMessages, new Comparator<LogMessage>() { + @Override + public int compare(LogMessage message1, LogMessage message2) { + return message1.getTimestamp().compareTo(message2.getTimestamp()); + } + }); + + return logMessages; } - public static void main(String[] args) { + @POST + @Path("{" + QUERY_PARAM_VOLUME_NAME + "}/" + SUBRESOURCE_DISKS) + public Status addDisks(@PathParam(QUERY_PARAM_VOLUME_NAME) String volumeName, @FormParam(QUERY_PARAM_DISKS) String disks) { + + List<String> diskList = Arrays.asList( disks.split(",") ); // Convert from comma separated sting (query parameter) to list + Status status = createDirectories(diskList, volumeName); + if (status.isSuccess()) { + List<String> bricks = Arrays.asList(status.getMessage().split(" ")); + status = glusterUtil.addBricks(volumeName, bricks); + + if (!status.isSuccess()) { + Status cleanupStatus = cleanupDirectories(diskList, volumeName, diskList.size(), "-d"); // Remove the directories if created + if (!cleanupStatus.isSuccess()) { + // append cleanup error to prepare brick error + status.setMessage(status.getMessage() + CoreConstants.NEWLINE + cleanupStatus.getMessage()); + } + } + } + return status; + } + + private String getBrickForDisk(Volume volume, String diskName) { + int index = volume.getDisks().indexOf(diskName); + return volume.getBricks().get(index); + } + + private String getDiskForBrick(Volume volume, String brickName) { + int index = volume.getBricks().indexOf(brickName); + return volume.getDisks().get(index); + } + + public static void main(String[] args) throws ClassNotFoundException { VolumesResource vr = new VolumesResource(); - VolumeListResponse response = vr.getAllVolumes(); - for (Volume volume : response.getVolumes()) { - System.out.println("\nName:" + volume.getName() + "\nType: " + volume.getVolumeTypeStr() + "\nStatus: " - + volume.getStatusStr()); - } + // VolumeListResponse response = vr.getAllVolumes(); + // for (Volume volume : response.getVolumes()) { + // System.out.println("\nName:" + volume.getName() + "\nType: " + volume.getVolumeTypeStr() + "\nStatus: " + // + volume.getStatusStr()); + // } + Volume volume = new Volume(); + volume.setName("vol3"); + volume.setTransportType(TRANSPORT_TYPE.ETHERNET); + List<String> disks = new ArrayList<String>(); + disks.add("192.168.1.210:sdb"); + volume.addDisks(disks); + volume.setAccessControlList("192.168.*"); + // Status status = vr.createVolume(volume); + // System.out.println(status.getMessage()); + Form form = new Form(); + form.add("volumeName", volume.getName()); + form.add(RESTConstants.FORM_PARAM_DELETE_OPTION, 1); + Status status = vr.deleteVolume("Vol2", "1"); + System.out.println("Code : " + status.getCode()); + System.out.println("Message " + status.getMessage()); } } diff --git a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/utils/GlusterUtil.java b/src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/utils/GlusterUtil.java index 35bba55d..30f73595 100644 --- a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/utils/GlusterUtil.java +++ b/src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/utils/GlusterUtil.java @@ -18,7 +18,7 @@ * along with this program. If not, see * <http://www.gnu.org/licenses/>. */ -package com.gluster.storage.management.core.utils; +package com.gluster.storage.management.server.utils; import java.util.ArrayList; import java.util.List; @@ -34,6 +34,8 @@ import com.gluster.storage.management.core.model.Volume; import com.gluster.storage.management.core.model.Volume.TRANSPORT_TYPE; import com.gluster.storage.management.core.model.Volume.VOLUME_STATUS; import com.gluster.storage.management.core.model.Volume.VOLUME_TYPE; +import com.gluster.storage.management.core.utils.ProcessResult; +import com.gluster.storage.management.core.utils.ProcessUtil; public class GlusterUtil { private static final String glusterFSminVersion = "3.1"; @@ -46,10 +48,14 @@ public class GlusterUtil { 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_OPTION_AUTH_ALLOW = "auth.allow:"; + 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 ProcessUtil processUtil = new ProcessUtil(); /** @@ -165,23 +171,22 @@ public class GlusterUtil { VOLUME_TYPE volType = volume.getVolumeType(); if (volType == VOLUME_TYPE.DISTRIBUTED_MIRROR) { volumeType = "replica"; - count = 2; + count = volume.getReplicaCount(); } else if (volType == VOLUME_TYPE.DISTRIBUTED_STRIPE) { volumeType = "stripe"; - count = 4; + count = volume.getStripeCount(); } String transportTypeStr = null; TRANSPORT_TYPE transportType = volume.getTransportType(); transportTypeStr = (transportType == TRANSPORT_TYPE.ETHERNET) ? "tcp" : "rdma"; - List<String> command = prepareVolumeCreateCommand(volume, bricks, count, volumeType, transportTypeStr); ProcessResult result = processUtil.executeCommand(command); - if(!result.isSuccess()) { + if (!result.isSuccess()) { // TODO: Perform cleanup on all nodes before returning return new Status(result); } - + return createOptions(volume); } @@ -202,7 +207,7 @@ public class GlusterUtil { return command; } - private Status createOptions(Volume volume) { + public Status createOptions(Volume volume) { Map<String, String> options = volume.getOptions(); if (options != null) { for (Entry<String, String> option : options.entrySet()) { @@ -229,6 +234,10 @@ public class GlusterUtil { return new Status(processUtil.executeCommand(command)); } + public Status deleteVolume(String volumeName) { + return new Status(processUtil.executeCommand("gluster", "--mode=script", "volume", "delete", volumeName)); + } + private String getVolumeInfo(String volumeName) { ProcessResult result = new ProcessUtil().executeCommand("gluster", "volume", "info", volumeName); if (!result.isSuccess()) { @@ -246,17 +255,40 @@ public class GlusterUtil { } return result.getOutput(); } - + private boolean readVolumeType(Volume volume, String line) { String volumeType = extractToken(line, VOLUME_TYPE_PFX); if (volumeType != null) { - volume.setVolumeType((volumeType.equals("Distribute")) ? VOLUME_TYPE.PLAIN_DISTRIBUTE - : VOLUME_TYPE.DISTRIBUTED_MIRROR); // TODO: for Stripe + if (volumeType.equals(VOLUME_TYPE_DISTRIBUTE)) { + volume.setVolumeType(VOLUME_TYPE.PLAIN_DISTRIBUTE); + } else if (volumeType.equals(VOLUME_TYPE_REPLICATE)) { + volume.setVolumeType(VOLUME_TYPE.DISTRIBUTED_MIRROR); + volume.setReplicaCount(Volume.DEFAULT_REPLICA_COUNT); + } else { + volume.setVolumeType(VOLUME_TYPE.DISTRIBUTED_STRIPE); + volume.setStripeCount(Volume.DEFAULT_STRIPE_COUNT); + } return true; } return false; } - + + + private void readReplicaOrStripeCount(Volume volume, String line) { + if (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.DISTRIBUTED_STRIPE) { + volume.setStripeCount(count); + } else if (volume.getVolumeType() == VOLUME_TYPE.DISTRIBUTED_MIRROR) { + volume.setReplicaCount(count); + volume.setStripeCount(0); + } + + } + return; + } + private boolean readVolumeStatus(Volume volume, String line) { String volumeStatus = extractToken(line, VOLUME_STATUS_PFX); if (volumeStatus != null) { @@ -265,38 +297,62 @@ public class GlusterUtil { } return false; } - + private boolean readTransportType(Volume volume, String line) { String transportType = extractToken(line, VOLUME_TRANSPORT_TYPE_PFX); if (transportType != null) { - volume.setTransportType(transportType.equals("tcp") ? TRANSPORT_TYPE.ETHERNET - : TRANSPORT_TYPE.INFINIBAND); + volume.setTransportType(transportType.equals("tcp") ? TRANSPORT_TYPE.ETHERNET : TRANSPORT_TYPE.INFINIBAND); return true; } return false; } - + private boolean readBrick(Volume volume, String line) { if (line.matches("Brick[0-9]+:.*")) { // line: "Brick1: server1:/export/md0/volume-name" - volume.addDisk(line.split(":")[2].trim().split("/")[2].trim()); + String[] brickParts = line.split(":"); + String serverName = brickParts[1].trim(); + String brickDir = brickParts[2].trim(); + + volume.addBrick(serverName + ":" + brickDir); + detectAndAddDiskToVolume(volume, serverName, brickDir); return true; } return false; } - + + + private void detectAndAddDiskToVolume(Volume volume, String serverName, String brickDir) { + // brick directory should be of the form /export/<diskname>/volume-name + try { + volume.addDisk(serverName + ":" + brickDir.split("/")[2].trim()); + } catch (ArrayIndexOutOfBoundsException e) { + // brick directory of a different form, most probably created manually + // connect to the server and get disk for the brick directory + Status status = new ServerUtil().getDiskForDir(serverName, brickDir); + if (status.isSuccess()) { + volume.addDisk(serverName + ":" + status.getMessage()); + } else { + // Couldn't fetch disk for the brick directory. Log error and add "unknown" as disk name. + System.out.println("Couldn't fetch disk name for brick [" + serverName + ":" + brickDir + "]"); + volume.addDisk(serverName + ":unknown"); + } + } + } + + private boolean readBrickGroup(String line) { - return extractToken(line, VOLUME_BRICKS_GROUP_PFX) != null; + return extractToken(line, VOLUME_BRICKS_GROUP_PFX) != null; } - + private boolean readOptionReconfigGroup(String line) { return extractToken(line, VOLUME_OPTIONS_RECONFIG_PFX) != null; } - + private boolean readOption(Volume volume, String line) { - if (line.matches("^[^:]*:[^:]*$")) { - String[] parts = line.split(":"); - volume.setOption(parts[0].trim(), parts[1].trim()); + if (line.matches("^[^:]*:.*$")) { + int index = line.indexOf(':'); + volume.setOption(line.substring(0, index).trim(), line.substring(index + 1, line.length()).trim()); return true; } return false; @@ -304,7 +360,7 @@ public class GlusterUtil { public Volume getVolume(String volumeName) { List<Volume> volumes = parseVolumeInfo(getVolumeInfo(volumeName)); - if(volumes.size() > 0) { + if (volumes.size() > 0) { return volumes.get(0); } return null; @@ -324,7 +380,6 @@ public class GlusterUtil { String volumeName = extractToken(line, VOLUME_NAME_PFX); if (volumeName != null) { if (volume != null) { - // add the previously read volume to volume list volumes.add(volume); } @@ -337,11 +392,13 @@ public class GlusterUtil { if (readVolumeType(volume, line)) continue; + if (extractToken(line, VOLUME_NUMBER_OF_BRICKS) != null) { + readReplicaOrStripeCount(volume, line); + } if (readVolumeStatus(volume, line)) continue; - if(readTransportType(volume, line)) + if (readTransportType(volume, line)) continue; - if (readBrickGroup(line)) { isBricksGroupFound = true; continue; @@ -361,7 +418,7 @@ public class GlusterUtil { } if (isOptionReconfigFound) { - if(readOption(volume, line)) { + if (readOption(volume, line)) { continue; } else { isOptionReconfigFound = false; @@ -375,8 +432,51 @@ public class GlusterUtil { return volumes; } + + public Status addBricks(String volumeName, List<String> bricks) { + List<String> command = new ArrayList<String>(); + command.add("gluster"); + command.add("volume"); + command.add("add-brick"); + command.add(volumeName); + command.addAll(bricks); + return new Status(processUtil.executeCommand(command)); + } + + + public String getLogLocation(String volumeName, String brickName) { + ProcessResult result = new ProcessUtil().executeCommand("gluster", "volume", "log", "locate", volumeName, + brickName); + if (!result.isSuccess()) { + throw new GlusterRuntimeException("Command [gluster volume info] failed with error: [" + + result.getExitValue() + "][" + result.getOutput() + "]"); + } + String output = result.getOutput(); + if (output.startsWith(VOLUME_LOG_LOCATION_PFX)) { + return output.substring(VOLUME_LOG_LOCATION_PFX.length()).trim(); + } + + throw new GlusterRuntimeException("Couldn't parse output of [volume log locate] command. [" + output + + "] doesn't start with prefix [" + VOLUME_LOG_LOCATION_PFX + "]"); + } + + public String getLogFileNameForBrickDir(String brickDir) { + String logFileName = brickDir; + if (logFileName.startsWith(CoreConstants.FILE_SEPARATOR)) { + logFileName = logFileName.replaceFirst(CoreConstants.FILE_SEPARATOR, ""); + } + logFileName = logFileName.replaceAll(CoreConstants.FILE_SEPARATOR, "-") + ".log"; + return logFileName; + } + + public static void main(String args[]) { // List<String> names = new GlusterUtil().getGlusterServerNames(); // System.out.println(names); + List<String> disks = new ArrayList<String>(); + disks.add("server1:sda"); + disks.add("server1:sdb"); + Status status = new GlusterUtil().addBricks("Volume3", disks); + System.out.println(status); } } diff --git a/src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/utils/ServerUtil.java b/src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/utils/ServerUtil.java index 645b7991..5e423f1c 100644 --- a/src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/utils/ServerUtil.java +++ b/src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/utils/ServerUtil.java @@ -49,6 +49,7 @@ public class ServerUtil { private static final String SCRIPT_DIR = "scripts"; private static final String SCRIPT_COMMAND = "python"; + private static final String REMOTE_SCRIPT_GET_DISK_FOR_DIR = "get_disk_for_dir.py"; public ProcessResult executeGlusterScript(boolean runInForeground, String scriptName, List<String> arguments) { List<String> command = new ArrayList<String>(); @@ -73,6 +74,7 @@ public class ServerUtil { * @param expectedClass Class of the object expected from script execution * @return Response from remote execution of the command */ + @SuppressWarnings("rawtypes") public Object executeOnServer(boolean runInForeground, String serverName, String commandWithArgs, Class expectedClass) { StringBuffer output = new StringBuffer(); try { @@ -91,8 +93,6 @@ public class ServerUtil { } connection.close(); - System.out.println("The ouput string is : " + output.toString()); - return unmarshal(expectedClass, output.toString(), expectedClass != Status.class); } catch(Exception e) { // any other exception means unexpected error. return status with error from exception. @@ -112,6 +112,7 @@ public class ServerUtil { * class Status. If that also fails, a status object with exception message is created and returned. * @return Object of given expected class, or a status object in case first unmarshalling fails. */ + @SuppressWarnings("rawtypes") private Object unmarshal(Class expectedClass, String input, boolean tryStatusOnFailure) { try { // create JAXB context and instantiate marshaller @@ -133,4 +134,13 @@ public class ServerUtil { // CreateVolumeExportDirectory.py md0 testvol System.out.println(new ServerUtil().executeOnServer(true, "localhost", "python CreateVolumeExportDirectory.py md0 testvol", Status.class)); } + + /** + * @param serverName Server on which the directory is present + * @param brickDir Directory whose disk is to be fetched + * @return Status object containing the disk name, or error message in case the remote script fails. + */ + public Status getDiskForDir(String serverName, String brickDir) { + return (Status) executeOnServer(true, serverName, REMOTE_SCRIPT_GET_DISK_FOR_DIR + " " + brickDir, Status.class); + } }
\ No newline at end of file |
