/**
* This file is part of ElasticDroid.
*
* ElasticDroid 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.
* ElasticDroid 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 ElasticDroid. If not, see <http://www.gnu.org/licenses/>.
*
* Authored by Siddhu Warrier on 5 Dec 2010
*/
package org.elasticdroid.model;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import org.elasticdroid.model.ds.SerializableInstance;
import org.elasticdroid.model.tpl.GenericModel;
import org.elasticdroid.tpl.GenericActivity;
import org.elasticdroid.tpl.GenericListActivity;
import org.elasticdroid.utils.DialogConstants;
import android.util.Log;
import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.ec2.AmazonEC2Client;
import com.amazonaws.services.ec2.model.DescribeInstancesRequest;
import com.amazonaws.services.ec2.model.DescribeRegionsRequest;
import com.amazonaws.services.ec2.model.Filter;
import com.amazonaws.services.ec2.model.Instance;
import com.amazonaws.services.ec2.model.Region;
import com.amazonaws.services.ec2.model.Reservation;
/**
* @author Siddhu Warrier
*
* 5 Dec 2010
*/
public class EC2InstancesModel extends GenericModel<Filter, Void, Object> {
/** AWS Connection data */
private HashMap<String, String> connectionData;
/** Tag for logging */
private static final String TAG = "org.elasticdroid.model.EC2InstancesModel";
/** Selected region */
private String selectedRegion;
/** The expected instance state code at which to stop autorefreshing */
private Integer expectedInstanceStateCode;
/** The maximum backoff period for the binary exponential backoff algo used with autorefresh.
* In milliseconds */
private static final long MAX_BACKOFF_PERIOD = 30000;
/**
* Start a new EC2InstancesModel object from a GenericListActivity
*
* @param genericActivity Of type GenericListActivity
* @param connectionData the data to use to connect to AWS.
* @param selectedRegion the selected region.
*/
public EC2InstancesModel(GenericListActivity genericActivity, HashMap<String, String>
connectionData, String selectedRegion) {
super(genericActivity);//call super class
this.connectionData = connectionData;
this.selectedRegion = selectedRegion;
}
/**
* Start a new ElasticIPsModel object from a GenericActivity
*
* @param genericActivity Of type GenericActivity
* @param connectionData the data to use to connect to AWS.
* @param selectedRegion the selected region.
*/
public EC2InstancesModel(GenericActivity genericActivity, HashMap<String, String>
connectionData, String selectedRegion) {
super(genericActivity);//call super class
this.connectionData = connectionData;
this.selectedRegion = selectedRegion;
}
/**
* Overloaded GenericActivity contructor which is used to enable autorefresh until state
* changes to the specified state.
*
* @param genericActivity Of type GenericActivity
* @param connectionData the data to use to connect to AWS.
* @param selectedRegion the selected region.
* @param expectedInstanceStateCode the instance state code at which we should stop
* auto-refreshing.
*/
public EC2InstancesModel(GenericActivity genericActivity, HashMap<String, String>
connectionData, String selectedRegion, int expectedInstanceStateCode) {
super(genericActivity);
this.connectionData = connectionData;
this.selectedRegion = selectedRegion;
this.expectedInstanceStateCode = expectedInstanceStateCode;
}
/**
* Overloaded contructor which is used to enable autorefresh until state
* changes to the specified state (uses GenericListActivity).
*
* @param genericActivity Of type GenericActivity
* @param connectionData the data to use to connect to AWS.
* @param selectedRegion the selected region.
* @param expectedInstanceStateCode the instance state code at which we should stop
* auto-refreshing.
*/
public EC2InstancesModel(GenericListActivity genericListActivity, HashMap<String, String>
connectionData, String selectedRegion, int expectedInstanceStateCode) {
super(genericListActivity);
this.connectionData = connectionData;
this.selectedRegion = selectedRegion;
this.expectedInstanceStateCode = expectedInstanceStateCode;
}
/**
* Execute the model in the background thread.
* Calls @link{EC2InstancesModel#getInstances}.
*
* @see android.os.AsyncTask#doInBackground(Params[])
*/
@Override
protected Object doInBackground(Filter... filters) {
//if expectedInstanceStateCode is not null and a single INSTANCE filter is passed,
//start the waitForInstanceStateChange method
if (expectedInstanceStateCode != null) {
if (filters.length != 1) {
return new IllegalArgumentException("Filter length cannot be greater than 1" +
"for auto-refresh.");
}
if (!filters[0].getName().equals("instance-id")) {
return new IllegalArgumentException("Can only auto-refresh filters of type" +
"\"instance-id\".");
}
if (filters[0].getValues().size() != 1) {
return new IllegalArgumentException("Cannot auto-refresh more than 1 instance.");
}
//if all ofthese conditions are met, start the waitForInstanceStateChange(...) method
return waitForInstanceStateChange(filters[0]);
}
else {
return getInstances(filters);
}
}
/**
*
* @param filters
* @return This method can return:
* <ul>
* <li>ArrayList<SerializableInstance>: If all goes well</li>
* <li>AmazonClientException: If there's connectivity problems on the client.</li>
* <li>AmazonServiceException: If there's AWS service problems.</li>
* <li>IllegalArgumentException: If the region can't be found.</li>
* </ul>
*/
public Object getInstances(Filter... filters) {
ArrayList<SerializableInstance> serInstances = new ArrayList<SerializableInstance>();
//result passed to Activity
List<Region> regions;
List<Reservation> reservations; //restult from EC2
//create credentials using the BasicAWSCredentials class
BasicAWSCredentials credentials = new BasicAWSCredentials(connectionData.get("accessKey"),
connectionData.get("secretAccessKey"));
//create Amazon EC2 Client object, and set tye end point to the region. params[3]
//contains endpoint
AmazonEC2Client amazonEC2Client = new AmazonEC2Client(credentials);
//1. create a filter for this region name
Filter regionFilter = new Filter("region-name");
regionFilter.setValues(new ArrayList<String>(Arrays.asList(
new String[]{selectedRegion})));
//2. query using this filter
try {
regions = amazonEC2Client.describeRegions(new DescribeRegionsRequest().
withFilters(regionFilter)).getRegions();
}
catch(AmazonServiceException exc) {
return exc;
}
catch(AmazonClientException exc) {
return exc;
}
//3. Make sure the region was found.
if (regions.size() != 1) {
return new IllegalArgumentException("Invalid region passed to model.");
}
Log.v(TAG + ".doInBackground()", "endpoint for region : " +
selectedRegion + "=" + regions.get(0).getEndpoint());
//set the endpoint
amazonEC2Client.setEndpoint(regions.get(0).getEndpoint());
//now get the instances
Log.v(TAG, "Size of filters:" + filters.length);
DescribeInstancesRequest request = new DescribeInstancesRequest();
request.setFilters(Arrays.asList(filters));
//get the list of instances using this filter
try {
reservations = amazonEC2Client.describeInstances(request).
getReservations();
}
catch(AmazonServiceException amazonServiceException) {
return amazonServiceException;
}
catch(AmazonClientException amazonClientException) {
return amazonClientException;
}
//add each instance found into the list of instances to return to the view
for (Reservation reservation: reservations) {
List<String> securityGroups = reservation.getGroupNames();
//note to self: List is an interface ArrayList implements.
//for each reservation, get the list of instances associated
for (Instance instance: reservation.getInstances()) {
serInstances.add(new SerializableInstance(instance, securityGroups));
}
}
return serInstances;
}
/**
* Called in *UI Thread* before doInBackground executes in a separate thread.
*
* Overriden to prevent progress dialog from being shown if expected Instance State Code
* is null
*/
@Override
protected void onPreExecute() {
if (expectedInstanceStateCode == null) {
if (!listActivityUsed) {
activity.showDialog(DialogConstants.PROGRESS_DIALOG.ordinal()); //the argument is
//not used
}
else {
listActivity.showDialog(DialogConstants.PROGRESS_DIALOG.ordinal()); //the argument
//is not used
}
}
else {
Log.v(TAG, "not displaying progress dialog as autorefresh operation being execd.");
}
}
/**
* Wait for instance state change. This can be used to repeatedly refresh a single instance
* until its state changes from startState to endState
* @param filters
*/
@SuppressWarnings("unchecked")
public Object waitForInstanceStateChange(Filter filter) {
ArrayList<SerializableInstance> serInstances;
//exponentialBackoff period to use
long backoffPeriod = 50;
long sleepPeriod;
//teh power of 2 to use in the binary exponential backoff algo
int multiplier = 0;
//keep refreshing till the state of the instance is as expected.
while(true) {
Object result = getInstances(filter);
if (result instanceof ArrayList<?>) {
serInstances = (ArrayList<SerializableInstance>) result;
//we know the list will have a size() = 1.
if (serInstances.get(0).getStateCode() == expectedInstanceStateCode) {
//stop auto-refreshing if the state of the instance is as expected
return result;
}
else { //the instance is still not in the right state.
//use the exponential backoff algo to sleep the thread for a specific period
sleepPeriod = backoffPeriod * (int)((Math.random()* Math.pow(2, multiplier)));
Log.v(TAG, "Backoff: Sleep for " + sleepPeriod + "msecs");
//if hte backoff interval is getting too long, just call the autorefresh off.
if (sleepPeriod >= MAX_BACKOFF_PERIOD) {
return null;
}
try {
Thread.sleep(sleepPeriod);
} catch (InterruptedException e) {
return null; //if interrupted, just stop executing
}
}
}
else {
//not expected result; probably exception.
//return the result and let the view worry about it.
return result;
}
multiplier ++; //increase the binary exponential backoff algo multiplier
}
}
}