/**
* Copyright 2010 the original author or authors.
*
* 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://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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 datameer.awstasks.aws.ec2;
import java.io.File;
import java.io.IOException;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.log4j.Logger;
import awstasks.com.amazonaws.services.ec2.AmazonEC2;
import awstasks.com.amazonaws.services.ec2.model.Instance;
import awstasks.com.amazonaws.services.ec2.model.InstanceStateName;
import awstasks.com.amazonaws.services.ec2.model.IpPermission;
import awstasks.com.amazonaws.services.ec2.model.Reservation;
import awstasks.com.amazonaws.services.ec2.model.RunInstancesRequest;
import awstasks.com.amazonaws.services.ec2.model.StartInstancesRequest;
import awstasks.com.amazonaws.services.ec2.model.StopInstancesRequest;
import awstasks.com.amazonaws.services.ec2.model.StopInstancesResult;
import awstasks.com.amazonaws.services.ec2.model.TerminateInstancesRequest;
import awstasks.com.amazonaws.services.ec2.model.TerminateInstancesResult;
import datameer.awstasks.aws.ec2.ssh.SshClient;
import datameer.awstasks.aws.ec2.ssh.SshClientImpl;
import datameer.awstasks.ssh.JschRunner;
import datameer.awstasks.util.Ec2Util;
import datameer.awstasks.util.ExceptionUtil;
import datameer.awstasks.util.Filters;
public class InstanceGroupImpl implements InstanceGroup {
private static Logger LOG = Logger.getLogger(InstanceGroupImpl.class);
private final AmazonEC2 _ec2;
private final boolean _includeMultipleReservations;
private List<awstasks.com.amazonaws.services.ec2.model.Instance> _instances;
public InstanceGroupImpl(AmazonEC2 ec2) {
this(ec2, false);
}
public InstanceGroupImpl(AmazonEC2 ec2, boolean includeMultipleReservations) {
_ec2 = ec2;
_includeMultipleReservations = includeMultipleReservations;
}
@Override
public void connectTo(String groupName) {
checkEc2Association(false);
LOG.info(String.format("connecting to instances of group '%s'", groupName));
_instances = Ec2Util.findByGroup(_ec2, groupName, _includeMultipleReservations, InstanceStateName.Pending, InstanceStateName.Running);
if (_instances == null) {
throw new IllegalArgumentException("no instances of group '" + groupName + "' running");
}
if (!InstanceStateName.Running.name().equalsIgnoreCase(_instances.get(0).getState().getName())) {
waitUntilServerUp(TimeUnit.MINUTES, 10);
}
}
@Override
public void connectTo(Reservation reservation) {
checkEc2Association(false);
LOG.info(String.format("connecting to reservation '%s'", reservation.getReservationId()));
_instances = reservation.getInstances();
updateInstanceDescriptions();
}
@Override
public Reservation launch(RunInstancesRequest launchConfiguration) {
return launch(launchConfiguration, null, 0);
}
@Override
public Reservation launch(RunInstancesRequest launchConfiguration, TimeUnit timeUnit, long time) {
checkEc2Association(false);
LOG.info(String.format("launching %d to %d instances with %s in groups %s...", launchConfiguration.getMinCount(), launchConfiguration.getMaxCount(), launchConfiguration.getImageId(),
launchConfiguration.getSecurityGroups()));
Reservation reservation = _ec2.runInstances(launchConfiguration).getReservation();
_instances = reservation.getInstances();
List<String> instanceIds = Ec2Util.toIds(_instances);
LOG.info(String.format("triggered launch of %d instances: %s", instanceIds.size(), instanceIds));
if (timeUnit != null) {
waitUntilServerUp(timeUnit, time);
LOG.info(String.format("launched %d instances: %s / %s", instanceIds.size(), instanceIds, Ec2Util.toPublicDns(_instances)));
}
return Ec2Util.reloadReservation(_ec2, reservation);
}
@Override
public Reservation start(List<String> instanceIds, TimeUnit timeUnit, long time) {
checkEc2Association(false);
LOG.info(String.format("starting %s instances ...", instanceIds));
_ec2.startInstances(new StartInstancesRequest(instanceIds));
Reservation reservation = Ec2Util.getReservation(_ec2, instanceIds);
_instances = reservation.getInstances();
if (timeUnit != null) {
waitUntilServerUp(timeUnit, time);
LOG.info(String.format("started %d instances: %s / %s", instanceIds.size(), instanceIds, Ec2Util.toPublicDns(_instances)));
}
return Ec2Util.reloadReservation(_ec2, reservation);
}
private void checkEc2Association(boolean shouldBeAssociated) {
if (shouldBeAssociated && !isAssociated()) {
throw new IllegalStateException("instance group is not yet associated with ec2 instances");
}
if (!shouldBeAssociated && isAssociated()) {
throw new IllegalStateException("instance group already associated with ec2 instances");
}
}
@Override
public boolean isAssociated() {
return _instances != null;
}
@Override
public int instanceCount() {
checkEc2Association(true);
return _instances.size();
}
@Override
public void terminate() {
checkEc2Association(true);
TerminateInstancesResult result = _ec2.terminateInstances(new TerminateInstancesRequest(Ec2Util.toIds(_instances)));
_instances = null;
LOG.info("terminated " + result.getTerminatingInstances().size() + " instances");
}
@Override
public void stop() {
checkEc2Association(true);
StopInstancesResult result = _ec2.stopInstances(new StopInstancesRequest(Ec2Util.toIds(_instances)));
_instances = null;
LOG.info("stopped " + result.getStoppingInstances().size() + " instances");
}
private List<Instance> waitUntilServerUp(TimeUnit timeUnit, long waitTime) {
_instances = Ec2Util.waitUntil(_ec2, _instances, EnumSet.of(InstanceStateName.Pending), InstanceStateName.Running, timeUnit, waitTime);
return _instances;
}
@Override
public List<Instance> getInstances(boolean updateBefore) {
checkEc2Association(true);
if (updateBefore) {
updateInstanceDescriptions();
}
return _instances;
}
private synchronized void updateInstanceDescriptions() {
_instances = Ec2Util.reloadInstanceDescriptions(_ec2, _instances);
}
@Override
public SshClient createSshClient(String username, File privateKey) {
return createSshClient(username, privateKey, true);
}
@Override
public SshClient createSshClient(String username, File privateKey, boolean usePublicDNS) {
List<String> instanceDns = checkSshPreconditions(usePublicDNS);
checkSshConnection(username, instanceDns, privateKey, null);
return new SshClientImpl(username, privateKey, instanceDns);
}
private List<String> checkSshPreconditions(boolean usePublicDNS) {
checkEc2Association(true);
updateInstanceDescriptions();
checkInstanceMode(_instances, InstanceStateName.Running);
List<String> instanceDns = usePublicDNS ? Ec2Util.toPublicDns(_instances):Ec2Util.toPrivateDns(_instances);
checkSshPermissions();
return instanceDns;
}
@Override
public SshClient createSshClient(String username, String password) {
return createSshClient(username, password, true);
}
@Override
public SshClient createSshClient(String username, String password, boolean usePublicDNS) {
List<String> instanceDns = checkSshPreconditions(usePublicDNS);
checkSshConnection(username, instanceDns, null, password);
return new SshClientImpl(username, password, instanceDns);
}
private void checkSshConnection(String username, List<String> instanceDns, File privateKey, String password) {
LOG.info("checking ssh connections of " + username + "@" + instanceDns);
for (String dns : instanceDns) {
JschRunner runner = new JschRunner(username, dns);
if (privateKey != null) {
runner.setKeyfile(new File(privateKey.getAbsolutePath()));
} else {
runner.setPassword(password);
}
runner.setTrust(true);
try {
runner.testConnect(TimeUnit.MINUTES.toMillis(5));
} catch (IOException e) {
throw ExceptionUtil.convertToRuntimeException(e);
}
}
}
private void checkSshPermissions() {
GroupPermission sshPermission = GroupPermission.createStandardSsh();
List<IpPermission> ipPermissions = Ec2Util.getPermissions(_ec2, Ec2Util.getSecurityGroups(_instances), Filters.permissionProtocol(sshPermission.getProtocol()));
if (ipPermissions.isEmpty()) {
throw new IllegalStateException("no permission for '" + sshPermission + "' set");
}
boolean foundMatching = false;
for (IpPermission ipPermission : ipPermissions) {
if (sshPermission.matches(ipPermission)) {
foundMatching = true;
break;
}
}
if (!foundMatching) {
throw new IllegalStateException("found permission for protocol '" + sshPermission.getProtocol() + " but with diverse ports (need " + sshPermission + ")");
}
}
private void checkInstanceMode(List<Instance> instances, InstanceStateName desiredMode) {
for (Instance instance : instances) {
if (!instance.getState().getName().equals(desiredMode.toString())) {
throw new IllegalStateException("instance " + instance.getInstanceId() + " is not in mode '" + desiredMode + "' but in mode '" + instance.getState() + "'");
}
}
}
}