/* * Copyright 2009-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.views.instances; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.swt.dnd.Clipboard; import org.eclipse.swt.dnd.TextTransfer; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.ui.statushandlers.StatusManager; import com.amazonaws.eclipse.core.AccountInfo; import com.amazonaws.eclipse.core.AwsToolkitCore; import com.amazonaws.eclipse.core.ui.IRefreshable; import com.amazonaws.eclipse.ec2.Ec2Plugin; import com.amazonaws.eclipse.ec2.InstanceType; import com.amazonaws.eclipse.ec2.InstanceTypes; import com.amazonaws.eclipse.ec2.keypairs.KeyPairManager; import com.amazonaws.eclipse.ec2.ui.SelectionTable; import com.amazonaws.eclipse.ec2.ui.ebs.CreateNewVolumeDialog; import com.amazonaws.eclipse.ec2.utils.DynamicMenuAction; import com.amazonaws.eclipse.ec2.utils.IMenu; import com.amazonaws.eclipse.ec2.utils.MenuAction; import com.amazonaws.eclipse.ec2.utils.MenuHandler; import com.amazonaws.services.ec2.model.DescribeInstancesRequest; import com.amazonaws.services.ec2.model.DescribeInstancesResult; import com.amazonaws.services.ec2.model.Instance; import com.amazonaws.services.ec2.model.Reservation; /** * Table displaying EC2 instances and a context menu with actions like opening * remote shells, terminating instances, rebooting instances, attaching EBS * volumes, etc. */ public class InstanceSelectionTable extends SelectionTable implements IRefreshable, IMenu { /* Menu Actions */ private Action refreshAction; private Action rebootAction; private Action terminateAction; private Action openShellAction; private Action openShellDialogAction; private Action createAmiAction; private Action copyPublicDnsNameAction; private Action attachNewVolumeAction; private Action startInstancesAction; private Action stopInstancesAction; /** Dropdown filter menu for Instance State */ private IAction instanceStateFilterDropDownAction; /** DropDown menu handler for Instance State */ private MenuHandler instanceStateDropDownMenuHandler; /** Dropdown filter menu for Security Group*/ private IAction securityGroupFilterDropDownAction; /** DropDown menu handler for Security Group */ private MenuHandler securityGroupDropDownMenuHandler; /** Holds the ALL option for Security Group Filter Item */ private MenuItem allSecurityGroupFilterItem; /** The timer we use to have this table automatically refreshed */ private RefreshTimer refreshInstanceListTimer; /** Shared account info */ final static AccountInfo accountInfo = AwsToolkitCore.getDefault().getAccountInfo(); /** Content and label provider for this selection table */ ViewContentAndLabelProvider contentAndLabelProvider; private KeyPairManager keyPairManager = new KeyPairManager(); /** * An optional field containing a list of Amazon EC2 instances to display in * this selection table */ private List<String> instancesToDisplay; /** Stores the no of instances that are displayed */ private int noOfInstances; /* * Public Interface */ /** * Creates a new instance selection table within the specified composite. * * @param parent * The parent composite for this new instance selection table. */ public InstanceSelectionTable(Composite parent) { super(parent, true, false); contentAndLabelProvider = new ViewContentAndLabelProvider(); viewer.setContentProvider(contentAndLabelProvider); viewer.setLabelProvider(contentAndLabelProvider); setComparator(new InstanceComparator(this, ViewContentAndLabelProvider.LAUNCH_TIME_COLUMN)); refreshInstanceListTimer = new RefreshTimer(this); refreshInstanceListTimer.startTimer(); } /** * Creates a new instance selection table within the specified composite, * displaying only the Amazon EC2 instance IDs specified. * * @param parent * The parent composite for this new instance selection table. * @param instancesToDisplay * A list of Amazon EC2 instance IDs to limit this selection * table to showing. */ public InstanceSelectionTable(Composite parent, List<String> instancesToDisplay) { this(parent); setInstancesToList(instancesToDisplay); } /** * Sets the period, in milliseconds, between automatic refreshes of the data * displayed in this instance selection table. * * @param refreshPeriodInMilliseconds * The period, in milliseconds, between automatic refreshes of * the data displayed in this table. */ public void setRefreshPeriod(int refreshPeriodInMilliseconds) { refreshInstanceListTimer.setRefreshPeriod(refreshPeriodInMilliseconds); } /** * Refreshes the list of a user's current instances. */ public void refreshInstances() { new RefreshInstancesThread().start(); } /** * Returns the Action object that refreshes this selection table. * * @return The IAction object that refreshes this selection table. */ public Action getRefreshAction() { return refreshAction; } /** * Returns the Action object that shows the Instance State filter dropdown menus * * @return The IAction object that shows the Instance State filter dropdown menus */ public IAction getInstanceStateFilterDropDownAction() { return instanceStateFilterDropDownAction; } /** * Returns the Action object that shows the Security Group filter dropdown menus * * @return The Action object that shows the Security Group filter dropdown menus */ public IAction getSecurityGroupFilterAction() { return securityGroupFilterDropDownAction; } /* (non-Javadoc) * @see org.eclipse.swt.widgets.Widget#dispose() */ @Override public void dispose() { refreshInstanceListTimer.stopTimer(); super.dispose(); } /* (non-Javadoc) * @see com.amazonaws.eclipse.ec2.ui.IRefreshable#refreshData() */ public void refreshData() { refreshInstances(); } /** * Sets the Amazon EC2 instances which should be displayed in this selection * table. Specifying an empty list or a null list will both result in * displaying no instances in this list. The selection table will be * refreshed by this method so that the specified instances are being * displayed. * * @param serviceInstances * A list of Amazon EC2 instance IDs to display in this selection * table. */ public void setInstancesToList(List<String> serviceInstances) { if (serviceInstances == null) { serviceInstances = new ArrayList<String>(); } this.instancesToDisplay = serviceInstances; this.refreshInstances(); } /* * SelectionTable Interface */ /* (non-Javadoc) * @see com.amazonaws.eclipse.ec2.ui.SelectionTable#createColumns() */ @Override protected void createColumns() { newColumn("Instance ID", 10); newColumn("Public DNS Name", 15); newColumn("Image ID", 10); newColumn("Root Device Type", 10); newColumn("State", 10); newColumn("Type", 10); newColumn("Availability Zone", 10); newColumn("Key Pair", 10); newColumn("Launch Time", 15); newColumn("Security Groups", 15); newColumn("Tags", 15); } /* (non-Javadoc) * @see com.amazonaws.eclipse.ec2.ui.SelectionTable#fillContextMenu(org.eclipse.jface.action.IMenuManager) */ @Override protected void fillContextMenu(IMenuManager manager) { manager.add(refreshAction); manager.add(new Separator()); manager.add(rebootAction); manager.add(terminateAction); manager.add(startInstancesAction); manager.add(stopInstancesAction); manager.add(new Separator()); manager.add(openShellAction); manager.add(openShellDialogAction); manager.add(new Separator()); manager.add(createAmiAction); manager.add(new Separator()); manager.add(copyPublicDnsNameAction); final boolean isRunningInstanceSelected = isRunningInstanceSelected(); final boolean exactlyOneInstanceSelected = isRunningInstanceSelected && (getAllSelectedInstances().size() == 1); if (exactlyOneInstanceSelected) { manager.add(new Separator()); manager.add(createEbsMenu()); } rebootAction.setEnabled(isRunningInstanceSelected); terminateAction.setEnabled(isRunningInstanceSelected); boolean instanceLaunchedWithKey = getSelectedInstance().getKeyName() != null; boolean knownPrivateKey = keyPairManager.isKeyPairValid(AwsToolkitCore.getDefault().getCurrentAccountId(), getSelectedInstance().getKeyName()); boolean canOpenShell = isRunningInstanceSelected && instanceLaunchedWithKey && knownPrivateKey; openShellAction.setEnabled(canOpenShell); openShellDialogAction.setEnabled(canOpenShell); copyPublicDnsNameAction.setEnabled(exactlyOneInstanceSelected); createAmiAction.setEnabled(exactlyOneInstanceSelected && instanceLaunchedWithKey); // These calls seem like a no-op, but it basically forces a refresh of the enablement state startInstancesAction.setEnabled(startInstancesAction.isEnabled()); stopInstancesAction.setEnabled(stopInstancesAction.isEnabled()); } /* (non-Javadoc) * @see com.amazonaws.eclipse.ec2.ui.SelectionTable#makeActions() */ @Override protected void makeActions() { refreshAction = new Action("Refresh", Ec2Plugin.getDefault().getImageRegistry().getDescriptor("refresh")) { public void run() { refreshInstances(); } public String getToolTipText() { return "Refresh instances"; } }; rebootAction = new RebootInstancesAction(this); terminateAction = new TerminateInstancesAction(this); openShellAction = new OpenShellAction(this); openShellDialogAction = new OpenShellDialogAction(this); createAmiAction = new CreateAmiAction(this); copyPublicDnsNameAction = new Action("Copy Public DNS Name", Ec2Plugin.getDefault().getImageRegistry().getDescriptor("clipboard")) { public void run() { copyPublicDnsNameToClipboard(getSelectedInstance()); } public String getToolTipText() { return "Copies this instance's public DNS name to the clipboard."; } }; attachNewVolumeAction = new Action("Attach New Volume...") { public void run() { Instance instance = getSelectedInstance(); CreateNewVolumeDialog dialog = new CreateNewVolumeDialog(Display.getCurrent().getActiveShell(), instance); if (dialog.open() != IDialogConstants.OK_ID) return; new AttachNewVolumeThread(instance, dialog.getSize(), dialog.getSnapshotId(), dialog.getDevice()).start(); } public String getToolTipText() { return "Attaches a new Elastic Block Storage volume to this instance."; } }; startInstancesAction = new StartInstancesAction(this); stopInstancesAction = new StopInstancesAction(this); instanceStateDropDownMenuHandler = new MenuHandler(); instanceStateDropDownMenuHandler.addListener(this); instanceStateDropDownMenuHandler.add("ALL", "All Instances", true); for (InstanceType instanceType : InstanceTypes.getInstanceTypes()) { instanceStateDropDownMenuHandler.add(instanceType.id, instanceType.name + " Instances"); } instanceStateDropDownMenuHandler.add("windows", "Windows Instances"); instanceStateFilterDropDownAction = new MenuAction("Status Filter", "Filter by instance state", "filter", instanceStateDropDownMenuHandler); securityGroupDropDownMenuHandler = new MenuHandler(); securityGroupDropDownMenuHandler.addListener(this); allSecurityGroupFilterItem = securityGroupDropDownMenuHandler.add("ALL", "All Security Groups", true); securityGroupFilterDropDownAction = new DynamicMenuAction("Security Groups Filter", "Filter by security group", "filter", securityGroupDropDownMenuHandler); } /* * Private Interface */ protected void copyPublicDnsNameToClipboard(Instance instance) { Clipboard clipboard = new Clipboard(Display.getCurrent()); String textData = instance.getPublicDnsName(); TextTransfer textTransfer = TextTransfer.getInstance(); Transfer[] transfers = new Transfer[]{textTransfer}; Object[] data = new Object[]{textData}; clipboard.setContents(data, transfers); clipboard.dispose(); } /** * Returns true if and only if at least one instance is selected and all of * the selected instances are in a running state. * * @return True if and only if at least one instance is selected and all of * the selected instances are in a running state, otherwise returns * false. */ private boolean isRunningInstanceSelected() { boolean instanceSelected = viewer.getTree().getSelectionCount() > 0; if (!instanceSelected) { return false; } for (TreeItem data : viewer.getTree().getSelection()) { Instance instance = (Instance)data.getData(); if (!instance.getState().getName().equalsIgnoreCase("running")) { return false; } } return true; } @SuppressWarnings("unchecked") List<Instance> getAllSelectedInstances() { StructuredSelection selection = (StructuredSelection)viewer.getSelection(); return (List<Instance>)selection.toList(); } private Instance getSelectedInstance() { StructuredSelection selection = (StructuredSelection)viewer.getSelection(); return (Instance)selection.getFirstElement(); } private IMenuManager createEbsMenu() { final MenuManager subMenu = new MenuManager("Elastic Block Storage"); subMenu.add(attachNewVolumeAction); subMenu.add(new Separator()); final Instance instance = getSelectedInstance(); new PopulateEbsMenuThread(instance, subMenu).start(); return subMenu; } /** * Sets the list of instances to be displayed in the instance table. * * @param instances * The list of instances to be displayed in the instance table. * @param securityGroupMap * A map of instance IDs to a list of security groups in which * those instances were launched. */ private void setInput(final List<Instance> instances, final Map<String, List<String>> securityGroupMap) { Display.getDefault().asyncExec(new Runnable() { @SuppressWarnings("unchecked") public void run() { /* * Sometimes we see cases where the content provider for a table * viewer is null, so we check for that here to avoid causing an * unhandled event loop exception. All InstanceSelectionTables * should always have a content provider set in the constructor, * but for some reason we occasionally still see this happen. */ if (viewer.getContentProvider() == null) { return; } StructuredSelection currentSelection = (StructuredSelection) viewer.getSelection(); viewer.setInput(new InstancesViewInput(instances, securityGroupMap)); packColumns(); Set<String> instanceIds = new HashSet<String>(); for (Instance instance : (List<Instance>) currentSelection.toList()) { instanceIds.add(instance.getInstanceId()); } List<Instance> newSelectedInstances = new ArrayList<Instance>(); for (TreeItem treeItem : viewer.getTree().getItems()) { Instance instance = (Instance) treeItem.getData(); if (instanceIds.contains(instance.getInstanceId())) { newSelectedInstances.add(instance); } } viewer.setSelection(new StructuredSelection(newSelectedInstances)); } }); } /* * Private Classes */ /** * Thread for making a service call to EC2 to list current instances. */ private class RefreshInstancesThread extends Thread { /* (non-Javadoc) * @see java.lang.Thread#run() */ @Override public void run() { synchronized (RefreshInstancesThread.class) { if (selectionTableListener != null) { selectionTableListener.loadingData(); enableDropDowns(false); } List<Reservation> reservations = null; try { boolean needsToDescribeInstances = true; DescribeInstancesRequest describeInstancesRequest = new DescribeInstancesRequest(); if (instancesToDisplay != null) { /* * If the caller explicitly asked for a list of zero * instances to be displayed, don't even bother querying for * anything. */ if (instancesToDisplay.size() == 0) { needsToDescribeInstances = false; }; describeInstancesRequest.setInstanceIds(instancesToDisplay); } final List<Instance> allInstances = new ArrayList<Instance>(); final Map<String, List<String>> securityGroupsByInstanceId = new HashMap<String, List<String>>(); if (needsToDescribeInstances) { DescribeInstancesResult response = getAwsEc2Client().describeInstances(describeInstancesRequest); reservations = response.getReservations(); noOfInstances = -1; //Reset the value Set<String> allSecurityGroups = new TreeSet<String>(); for (Reservation reservation : reservations) { List<Instance> instances = reservation.getInstances(); List<String> groupNames = reservation.getGroupNames(); Collections.sort(groupNames); allSecurityGroups.addAll(groupNames); //Filter Security Groups if (securityGroupDropDownMenuHandler.getCurrentSelection().getMenuId().equals("ALL") || groupNames.contains(securityGroupDropDownMenuHandler.getCurrentSelection().getMenuId())) { for (Instance instance : instances) { //Filter Instances if (!instanceStateDropDownMenuHandler.getCurrentSelection().getMenuId().equals("ALL")) { if (instanceStateDropDownMenuHandler.getCurrentSelection().getMenuId().equalsIgnoreCase("windows")) { if (instance.getPlatform() == null || !instance.getPlatform().equals("windows")) continue; } else if (!instance.getInstanceType().equalsIgnoreCase(instanceStateDropDownMenuHandler.getCurrentSelection().getMenuId())) { continue; } } allInstances.add(instance); // Populate the map of instance IDs -> security groups securityGroupsByInstanceId.put(instance.getInstanceId(), groupNames); } } } //Populate all Security Groups dynamically securityGroupDropDownMenuHandler.clear(); securityGroupDropDownMenuHandler.add(allSecurityGroupFilterItem); for(String securityGroup : allSecurityGroups) { securityGroupDropDownMenuHandler.add(new MenuItem(securityGroup, securityGroup)); } } noOfInstances = allInstances.size(); setInput(allInstances, securityGroupsByInstanceId); } 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(IStatus.ERROR, Ec2Plugin.PLUGIN_ID, "Unable to list instances: " + e.getMessage(), e); StatusManager.getManager().handle(status, StatusManager.LOG); } } finally { if (selectionTableListener != null) { selectionTableListener.finishedLoadingData(noOfInstances); enableDropDowns(true); } } } } } /** * Callback function. Is called from the DropdownMenuHandler when a menu option is clicked * * @param itemSelected The selected MenuItem * * @see com.amazonaws.eclipse.ec2.utils.IMenu#menuClicked(com.amazonaws.eclipse.ec2.utils.IMenu.MenuItem) */ public void menuClicked(MenuItem menuItemSelected) { refreshData(); } /** * Enables/Disables dropdown filters * * @param checked A TRUE value will imply the dropdown filter is enabled; FALSE value will make it disabled */ private void enableDropDowns(boolean checked) { instanceStateFilterDropDownAction.setEnabled(checked); securityGroupFilterDropDownAction.setEnabled(checked); } }