/* * Copyright 2008-2012 Amazon Technologies, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://aws.amazon.com/apache2.0 * * This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES * OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and * limitations under the License. */ package com.amazonaws.eclipse.ec2.ui.ebs; import java.text.DateFormat; import java.util.List; import org.eclipse.core.runtime.Status; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.viewers.ITableLabelProvider; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.Viewer; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.statushandlers.StatusManager; import com.amazonaws.eclipse.core.AwsToolkitCore; import com.amazonaws.eclipse.ec2.Ec2Plugin; import com.amazonaws.eclipse.ec2.TagFormatter; import com.amazonaws.eclipse.ec2.ui.SelectionTable; import com.amazonaws.eclipse.ec2.ui.SelectionTableComparator; import com.amazonaws.services.ec2.AmazonEC2; import com.amazonaws.services.ec2.model.CreateSnapshotRequest; import com.amazonaws.services.ec2.model.CreateVolumeRequest; import com.amazonaws.services.ec2.model.DeleteVolumeRequest; import com.amazonaws.services.ec2.model.DescribeVolumesRequest; import com.amazonaws.services.ec2.model.DetachVolumeRequest; import com.amazonaws.services.ec2.model.Volume; import com.amazonaws.services.ec2.model.VolumeAttachment; /** * Selection table subclass for displaying EBS volumes. */ public class VolumeSelectionTable extends SelectionTable { private static final int VOLUME_ID_COLUMN = 0; private static final int STATUS_COLUMN = 1; private static final int CREATE_TIME_COLUMN = 2; private static final int ATTACHED_INSTANCE_COLUMN = 3; private static final int SIZE_COLUMN = 4; private static final int ZONE_COLUMN = 5; private static final int SNAPSHOT_ID_COLUMN = 6; private static final int TAGS_COLUMN = 7; /** An Action implementation that releases a volume */ private Action releaseAction; /** An Action implementation that detaches a volume */ private Action detachAction; /** An Action implementation that creates a snapshot */ private Action createSnapshotAction; /** An Action implementation that refreshes the volume list */ private Action refreshAction; /** An Action implementation that creates a new volume */ private Action createAction; /** * An optional reference to a snapshot selection table that should be * refreshed when new snapshots are created. */ private SnapshotSelectionTable snapshotSelectionTable; /** * Content provider for the volume selection table. */ private static class VolumeContentProvider implements ITreeContentProvider { List<Volume> volumes; @SuppressWarnings("unchecked") public void inputChanged(Viewer v, Object oldInput, Object newInput) { volumes = (List<Volume>)newInput; } public void dispose() { } public Object[] getElements(Object parent) { if (volumes == null) { return new Object[0]; } return volumes.toArray(); } public Object[] getChildren(Object parentElement) { return new Object[0]; } public Object getParent(Object element) { return null; } public boolean hasChildren(Object element) { return false; } } /** * Label provider for entries in the volume selection table. */ private static class VolumeLabelProvider extends LabelProvider implements ITableLabelProvider { private final DateFormat dateFormat = DateFormat.getDateTimeInstance(); /* (non-Javadoc) * @see org.eclipse.jface.viewers.ITableLabelProvider#getColumnText(java.lang.Object, int) */ public String getColumnText(Object obj, int index) { Volume volume = (Volume)obj; if (volume == null) return ""; switch (index) { case VOLUME_ID_COLUMN: return volume.getVolumeId(); case STATUS_COLUMN: return volume.getState(); case CREATE_TIME_COLUMN: if (volume.getCreateTime() == null) return ""; return dateFormat.format(volume.getCreateTime()); case ATTACHED_INSTANCE_COLUMN: for (VolumeAttachment attachment : volume.getAttachments()) { return attachment.getInstanceId(); } return ""; case SIZE_COLUMN: return volume.getSize().toString(); case ZONE_COLUMN: return volume.getAvailabilityZone(); case SNAPSHOT_ID_COLUMN: return volume.getSnapshotId(); case TAGS_COLUMN: return TagFormatter.formatTags(volume.getTags()); } return "???"; } public Image getColumnImage(Object obj, int index) { if (index == 0) return Ec2Plugin.getDefault().getImageRegistry().get("volume"); return null; } public Image getImage(Object obj) { return null; } } /** * Comparator for sorting volumes by creation time. */ private static class VolumeComparator extends SelectionTableComparator { /** * @param defaultColumn */ public VolumeComparator(int defaultColumn) { super(defaultColumn); } /* (non-Javadoc) * @see com.amazonaws.eclipse.ec2.ui.SelectionTableComparator#compareIgnoringDirection(org.eclipse.jface.viewers.Viewer, java.lang.Object, java.lang.Object) */ @Override protected int compareIgnoringDirection(Viewer viewer, Object e1, Object e2) { if (!(e1 instanceof Volume && e2 instanceof Volume)) { return 0; } Volume volume1 = (Volume)e1; Volume volume2 = (Volume)e2; switch (this.sortColumn) { case VOLUME_ID_COLUMN: return volume1.getVolumeId().compareTo(volume2.getVolumeId()); case STATUS_COLUMN: return volume1.getState().compareTo(volume2.getState()); case CREATE_TIME_COLUMN: return volume1.getCreateTime().compareTo(volume2.getCreateTime()); case ATTACHED_INSTANCE_COLUMN: String instanceId1 = ""; String instanceId2 = ""; for (VolumeAttachment att : volume1.getAttachments()) instanceId1 = att.getInstanceId(); for (VolumeAttachment att : volume2.getAttachments()) instanceId2 = att.getInstanceId(); return instanceId1.compareTo(instanceId2); case SIZE_COLUMN: return volume1.getSize().compareTo(volume2.getSize()); case ZONE_COLUMN: return volume1.getAvailabilityZone().compareTo(volume2.getAvailabilityZone()); case SNAPSHOT_ID_COLUMN: return volume1.getSnapshotId().compareTo(volume2.getSnapshotId()); case TAGS_COLUMN: return TagFormatter.formatTags(volume1.getTags()).compareTo(TagFormatter.formatTags(volume2.getTags())); } return 0; } } /* * Public Interface */ /** * Instantiates a new volume selection table as a child of the specified * parent composite. * * @param parent * The parent composite for this new selection table. */ public VolumeSelectionTable(Composite parent) { super(parent); viewer.setContentProvider(new VolumeContentProvider()); viewer.setLabelProvider(new VolumeLabelProvider()); setComparator(new VolumeComparator(CREATE_TIME_COLUMN)); refreshVolumes(); } /** * Refreshes the volumes in this selection table. */ public void refreshVolumes() { if (viewer == null) return; new RefreshVolumesThread().start(); } /** * Sets the optional snapshot selection table that should be refreshed when * new snapshots are created from volumes. * * @param snapshotSelectionComposite * The snapshot selection table. */ public void setSnapshotSelectionTable(SnapshotSelectionTable snapshotSelectionComposite) { this.snapshotSelectionTable = snapshotSelectionComposite; } /** * Returns the currently selected volume. * * @return The currently selected volume. */ public Volume getSelectedVolume() { return (Volume)this.getSelection(); } /* * SelectionTable Interface */ /* (non-Javadoc) * @see com.amazonaws.eclipse.ec2.ui.SelectionTable#createColumns() */ @Override protected void createColumns() { newColumn("Volume ID", 10); newColumn("Status", 10); newColumn("Create Time", 10); newColumn("Attached Instance", 10); newColumn("Size (GB)", 10); newColumn("Zone", 10); newColumn("Snapshot ID", 10); newColumn("Tags", 15); } /* (non-Javadoc) * @see com.amazonaws.eclipse.ec2.ui.SelectionTable#fillContextMenu(org.eclipse.jface.action.IMenuManager) */ @Override protected void fillContextMenu(IMenuManager manager) { Volume selectedVolume = getSelectedVolume(); boolean isVolumeSelected = (selectedVolume != null); boolean isAvailableVolumeSelected = isVolumeSelected && selectedVolume.getState().equalsIgnoreCase("available"); boolean isAttachedVolumeSelected = isVolumeSelected && !(selectedVolume.getAttachments().isEmpty()); releaseAction.setEnabled(isAvailableVolumeSelected); detachAction.setEnabled(isAttachedVolumeSelected); createSnapshotAction.setEnabled(isVolumeSelected); manager.add(refreshAction); manager.add(new Separator()); manager.add(createAction); manager.add(releaseAction); manager.add(detachAction); manager.add(new Separator()); manager.add(createSnapshotAction); } /* (non-Javadoc) * @see com.amazonaws.eclipse.ec2.ui.SelectionTable#makeActions() */ @Override protected void makeActions() { refreshAction = new Action() { public void run() { refreshVolumes(); } }; refreshAction.setText("Refresh"); refreshAction.setToolTipText("Refresh the volume list"); refreshAction.setImageDescriptor(Ec2Plugin.getDefault().getImageRegistry().getDescriptor("refresh")); createAction = new Action() { public void run() { final CreateNewVolumeDialog dialog = new CreateNewVolumeDialog(Display.getCurrent().getActiveShell()); if (dialog.open() != IDialogConstants.OK_ID) return; new CreateVolumeThread(dialog.getAvailabilityZone(), dialog.getSize(), dialog.getSnapshotId()).start(); } }; createAction.setText("New Volume"); createAction.setToolTipText("Create a new EBS volume"); createAction.setImageDescriptor(Ec2Plugin.getDefault().getImageRegistry().getDescriptor("add")); releaseAction = new Action() { @SuppressWarnings("unchecked") @Override public void run() { StructuredSelection selection = (StructuredSelection)viewer.getSelection(); new ReleaseVolumesThread((List<Volume>)selection.toList()).start(); } }; releaseAction.setText("Release Volume"); releaseAction.setDescription("Release selected volume(s)"); releaseAction.setImageDescriptor(Ec2Plugin.getDefault().getImageRegistry().getDescriptor("remove")); detachAction = new Action() { @Override public void run() { final Volume volume = getSelectedVolume(); new DetachVolumeThread(volume).start(); } }; detachAction.setText("Detach Volume"); detachAction.setToolTipText("Detach the selected volume from all instances."); createSnapshotAction = new Action() { @Override public void run() { new CreateSnapshotThread(getSelectedVolume()).start(); } }; createSnapshotAction.setText("Create Snapshot"); createSnapshotAction.setToolTipText("Creates a new snapshot of this volume."); createSnapshotAction.setImageDescriptor(Ec2Plugin.getDefault().getImageRegistry().getDescriptor("snapshot")); } /* * Private Threads for making EC2 service calls */ /** * Thread for making an EC2 service call to refresh the list of EBS volumes. */ private class RefreshVolumesThread extends Thread { /* (non-Javadoc) * @see java.lang.Thread#run() */ @Override public void run() { try { if (selectionTableListener != null) selectionTableListener.loadingData(); final AmazonEC2 ec2 = getAwsEc2Client(); final List<Volume> volumes = ec2.describeVolumes(new DescribeVolumesRequest()).getVolumes(); Display.getDefault().asyncExec(new Runnable() { public void run() { viewer.setInput(volumes); packColumns(); } }); } catch (Exception e) { // Only log an error if the account info is valid and we // actually expected this call to work if (AwsToolkitCore.getDefault().getAccountInfo().isValid()) { Status status = new Status(Status.ERROR, Ec2Plugin.PLUGIN_ID, "Unable to refresh EBS volumes: " + e.getMessage(), e); StatusManager.getManager().handle(status, StatusManager.LOG); } } finally { if (selectionTableListener != null) selectionTableListener.finishedLoadingData(-1); } } } /** * Thread for making an EC2 service call to detach an EBS volume. */ private class DetachVolumeThread extends Thread { /** The volume to detach */ private final Volume volume; /** * Creates a new thread ready to be started to detach the specified * volume. * * @param volume * The EBS volume to detach. */ public DetachVolumeThread(final Volume volume) { this.volume = volume; } /* (non-Javadoc) * @see java.lang.Thread#run() */ @Override public void run() { for (VolumeAttachment attachmentInfo : volume.getAttachments()) { String volumeId = attachmentInfo.getVolumeId(); String instanceId = attachmentInfo.getInstanceId(); String device = attachmentInfo.getDevice(); try { DetachVolumeRequest request = new DetachVolumeRequest(); request.setVolumeId(volumeId); request.setInstanceId(instanceId); request.setDevice(device); request.setForce(false); getAwsEc2Client().detachVolume(request); } catch (Exception e) { Status status = new Status(Status.ERROR, Ec2Plugin.PLUGIN_ID, "Unable to detach EBS volume: " + e.getMessage(), e); StatusManager.getManager().handle(status, StatusManager.SHOW | StatusManager.LOG); } } refreshVolumes(); } } /** * Thread for making an EC2 service call to create a new EBS volume. */ private class CreateVolumeThread extends Thread { /** The zone to create the volume in */ private final String zoneName; /** The snapshot to create the volume from (optional) */ private final String snapshotId; /** The size of the new volume */ private final int size; /** * Creates a new thread ready to be started to create a new EBS volume. * * @param zoneName * The availability zone to create the new volume in. * @param size * The size of the new volume. * @param snapshotId * An optional snapshot id to create the volume from. */ public CreateVolumeThread(String zoneName, int size, String snapshotId) { this.zoneName = zoneName; this.size = size; this.snapshotId = snapshotId; } /* (non-Javadoc) * @see java.lang.Thread#run() */ @Override public void run() { try { CreateVolumeRequest request = new CreateVolumeRequest(); // Only set size if we're not using a snapshot if (snapshotId == null) request.setSize(size); request.setSnapshotId(snapshotId); request.setAvailabilityZone(zoneName); getAwsEc2Client().createVolume(request); refreshVolumes(); } catch (Exception e) { Status status = new Status(Status.ERROR, Ec2Plugin.PLUGIN_ID, "Unable to create EBS volume: " + e.getMessage(), e); StatusManager.getManager().handle(status, StatusManager.SHOW | StatusManager.LOG); } } } /** * Thread for making an EC2 service call to release EBS volumes. */ private class ReleaseVolumesThread extends Thread { /** The volumes to release */ private final List<Volume> volumes; /** * Creates a new thread ready to be started and release the specified * volumes. * * @param volumes * The EBS volumes to release. */ public ReleaseVolumesThread(final List<Volume> volumes) { this.volumes = volumes; } /* (non-Javadoc) * @see java.lang.Thread#run() */ @Override public void run() { try { AmazonEC2 ec2 = getAwsEc2Client(); for (Volume volume : volumes) { DeleteVolumeRequest request = new DeleteVolumeRequest(); request.setVolumeId(volume.getVolumeId()); ec2.deleteVolume(request); } } catch (Exception e) { Status status = new Status(Status.ERROR, Ec2Plugin.PLUGIN_ID, "Unable to release EBS volume: " + e.getMessage(), e); StatusManager.getManager().handle(status, StatusManager.SHOW | StatusManager.LOG); } refreshVolumes(); } } /** * Thread to make an EC2 service call to create a new snapshot from an EBS * volume. */ private class CreateSnapshotThread extends Thread { /** The volume to create a snapshot from */ private final Volume volume; /** * Creates a new thread ready to be started to create a snapshot of the * specified volume. * * @param volume * The volume to create a snapshot of. */ public CreateSnapshotThread(final Volume volume) { this.volume = volume; } /* (non-Javadoc) * @see java.lang.Thread#run() */ @Override public void run() { try { CreateSnapshotRequest request = new CreateSnapshotRequest(); request.setVolumeId(volume.getVolumeId()); getAwsEc2Client().createSnapshot(request); if (snapshotSelectionTable != null) { snapshotSelectionTable.refreshSnapshots(); } } catch (Exception e) { Status status = new Status(Status.ERROR, Ec2Plugin.PLUGIN_ID, "Unable to create snapshot: " + e.getMessage(), e); StatusManager.getManager().handle(status, StatusManager.SHOW | StatusManager.LOG); } } } }