1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.common.aws.ec2.impl;
17
18 import static com.google.common.base.Optional.absent;
19 import static com.google.common.base.Optional.fromNullable;
20 import static com.google.common.base.Preconditions.checkArgument;
21 import static com.google.common.base.Preconditions.checkNotNull;
22 import static com.google.common.base.Preconditions.checkState;
23 import static com.google.common.collect.Lists.newArrayList;
24 import static java.lang.String.format;
25 import static java.util.Collections.singletonList;
26 import static org.kuali.common.aws.ec2.model.InstanceStateName.RUNNING;
27 import static org.kuali.common.aws.ec2.model.InstanceStateName.STOPPED;
28 import static org.kuali.common.aws.ec2.model.InstanceStateName.TERMINATED;
29 import static org.kuali.common.util.FormatUtils.getMillisAsInt;
30 import static org.kuali.common.util.FormatUtils.getTime;
31 import static org.kuali.common.util.base.Exceptions.illegalState;
32 import static org.kuali.common.util.base.Precondition.checkNotBlank;
33 import static org.kuali.common.util.base.Precondition.checkNotNull;
34 import static org.kuali.common.util.base.Threads.sleep;
35 import static org.kuali.common.util.log.Loggers.newLogger;
36
37 import java.util.ArrayList;
38 import java.util.Collection;
39 import java.util.Collections;
40 import java.util.HashSet;
41 import java.util.List;
42 import java.util.Set;
43
44 import org.apache.commons.lang3.StringUtils;
45 import org.kuali.common.aws.ec2.api.EC2Service;
46 import org.kuali.common.aws.ec2.model.CreateAMIRequest;
47 import org.kuali.common.aws.ec2.model.EC2ServiceContext;
48 import org.kuali.common.aws.ec2.model.InstanceStateName;
49 import org.kuali.common.aws.ec2.model.LaunchInstanceContext;
50 import org.kuali.common.aws.ec2.model.Regions;
51 import org.kuali.common.aws.ec2.model.RootVolume;
52 import org.kuali.common.aws.ec2.model.security.KualiSecurityGroup;
53 import org.kuali.common.aws.ec2.model.security.Permission;
54 import org.kuali.common.aws.ec2.model.security.Protocol;
55 import org.kuali.common.aws.ec2.model.security.SetPermissionsResult;
56 import org.kuali.common.aws.ec2.model.status.InstanceStatusType;
57 import org.kuali.common.aws.ec2.model.status.InstanceStatusValue;
58 import org.kuali.common.aws.ec2.util.LaunchUtils;
59 import org.kuali.common.core.ssh.PublicKey;
60 import org.kuali.common.util.Assert;
61 import org.kuali.common.util.CollectionUtils;
62 import org.kuali.common.util.FormatUtils;
63 import org.kuali.common.util.SetUtils;
64 import org.kuali.common.util.base.Threads;
65 import org.kuali.common.util.condition.Condition;
66 import org.kuali.common.util.wait.DefaultWaitService;
67 import org.kuali.common.util.wait.WaitContext;
68 import org.kuali.common.util.wait.WaitResult;
69 import org.kuali.common.util.wait.WaitService;
70 import org.slf4j.Logger;
71
72 import com.amazonaws.auth.AWSCredentials;
73 import com.amazonaws.regions.RegionUtils;
74 import com.amazonaws.services.ec2.AmazonEC2Client;
75 import com.amazonaws.services.ec2.model.AuthorizeSecurityGroupIngressRequest;
76 import com.amazonaws.services.ec2.model.BlockDeviceMapping;
77 import com.amazonaws.services.ec2.model.CopyImageRequest;
78 import com.amazonaws.services.ec2.model.CopyImageResult;
79 import com.amazonaws.services.ec2.model.CreateSecurityGroupRequest;
80 import com.amazonaws.services.ec2.model.CreateSnapshotRequest;
81 import com.amazonaws.services.ec2.model.CreateSnapshotResult;
82 import com.amazonaws.services.ec2.model.CreateTagsRequest;
83 import com.amazonaws.services.ec2.model.DeleteSnapshotRequest;
84 import com.amazonaws.services.ec2.model.DeregisterImageRequest;
85 import com.amazonaws.services.ec2.model.DescribeImagesRequest;
86 import com.amazonaws.services.ec2.model.DescribeImagesResult;
87 import com.amazonaws.services.ec2.model.DescribeInstanceStatusRequest;
88 import com.amazonaws.services.ec2.model.DescribeInstanceStatusResult;
89 import com.amazonaws.services.ec2.model.DescribeInstancesRequest;
90 import com.amazonaws.services.ec2.model.DescribeInstancesResult;
91 import com.amazonaws.services.ec2.model.DescribeKeyPairsResult;
92 import com.amazonaws.services.ec2.model.DescribeSecurityGroupsRequest;
93 import com.amazonaws.services.ec2.model.DescribeSecurityGroupsResult;
94 import com.amazonaws.services.ec2.model.DescribeSnapshotsRequest;
95 import com.amazonaws.services.ec2.model.DescribeSnapshotsResult;
96 import com.amazonaws.services.ec2.model.EbsBlockDevice;
97 import com.amazonaws.services.ec2.model.EbsInstanceBlockDevice;
98 import com.amazonaws.services.ec2.model.Image;
99 import com.amazonaws.services.ec2.model.ImportKeyPairRequest;
100 import com.amazonaws.services.ec2.model.ImportKeyPairResult;
101 import com.amazonaws.services.ec2.model.Instance;
102 import com.amazonaws.services.ec2.model.InstanceBlockDeviceMapping;
103 import com.amazonaws.services.ec2.model.InstanceStatus;
104 import com.amazonaws.services.ec2.model.InstanceStatusDetails;
105 import com.amazonaws.services.ec2.model.InstanceStatusSummary;
106 import com.amazonaws.services.ec2.model.IpPermission;
107 import com.amazonaws.services.ec2.model.KeyPairInfo;
108 import com.amazonaws.services.ec2.model.ModifyInstanceAttributeRequest;
109 import com.amazonaws.services.ec2.model.Placement;
110 import com.amazonaws.services.ec2.model.Region;
111 import com.amazonaws.services.ec2.model.RegisterImageRequest;
112 import com.amazonaws.services.ec2.model.RegisterImageResult;
113 import com.amazonaws.services.ec2.model.Reservation;
114 import com.amazonaws.services.ec2.model.RevokeSecurityGroupIngressRequest;
115 import com.amazonaws.services.ec2.model.RunInstancesRequest;
116 import com.amazonaws.services.ec2.model.RunInstancesResult;
117 import com.amazonaws.services.ec2.model.SecurityGroup;
118 import com.amazonaws.services.ec2.model.Snapshot;
119 import com.amazonaws.services.ec2.model.StartInstancesRequest;
120 import com.amazonaws.services.ec2.model.StopInstancesRequest;
121 import com.amazonaws.services.ec2.model.Tag;
122 import com.amazonaws.services.ec2.model.TerminateInstancesRequest;
123 import com.google.common.base.Optional;
124 import com.google.common.collect.ImmutableList;
125 import com.google.common.collect.Lists;
126
127
128
129
130 public final class DefaultEC2Service implements EC2Service {
131
132 private static final Logger logger = newLogger();
133 private static final String SNAPSHOT_COMPLETED_STATE = "completed";
134 private static final String AMI_AVAILABLE_STATE = "available";
135
136
137 private final AmazonEC2Client client;
138
139 private final EC2ServiceContext context;
140 private final WaitService service;
141
142 public DefaultEC2Service(AWSCredentials credentials) {
143 this(credentials, new Region().withRegionName(Regions.DEFAULT_REGION.getName()));
144 }
145
146 public DefaultEC2Service(AWSCredentials credentials, String region) {
147 this(credentials, new Region().withRegionName(region));
148 }
149
150 public DefaultEC2Service(AWSCredentials credentials, Region region) {
151 this(EC2ServiceContext.create(credentials, region), new DefaultWaitService());
152 }
153
154 public DefaultEC2Service(EC2ServiceContext context, WaitService service) {
155 this.context = checkNotNull(context, "context");
156 this.service = checkNotNull(service, "service");
157 this.client = LaunchUtils.getClient(context);
158 }
159
160 @Override
161 public String getAccessKey() {
162 return context.getCredentials().getAWSAccessKeyId();
163 }
164
165 @Override
166 public String getRegion() {
167 return context.getRegion();
168 }
169
170 @Override
171 public String copyAmi(String region, String ami) {
172 return copyAmi(region, ami, Optional.<String> absent());
173 }
174
175 @Override
176 public String copyAmi(String region, String ami, String name) {
177 return copyAmi(region, ami, Optional.of(name));
178 }
179
180 public String copyAmi(String region, String ami, Optional<String> name) {
181 checkNotBlank(region, "region");
182 checkNotBlank(ami, "ami");
183 checkNotBlank(name, "name");
184 checkNotNull(RegionUtils.getRegion(region), "region %s is unknown", region);
185 CopyImageRequest request = new CopyImageRequest();
186 request.setSourceImageId(ami);
187 request.setSourceRegion(region);
188 if (name.isPresent()) {
189 request.setName(name.get());
190 }
191 CopyImageResult result = client.copyImage(request);
192 Threads.sleep(1000);
193 waitForAmiState(result.getImageId(), AMI_AVAILABLE_STATE, getMillisAsInt("4h"));
194 return result.getImageId();
195 }
196
197 @Override
198 public List<Image> getImages() {
199 return client.describeImages().getImages();
200 }
201
202 @Override
203 public void deleteSnapshot(String snapshotId) {
204 DeleteSnapshotRequest request = new DeleteSnapshotRequest();
205 request.setSnapshotId(snapshotId);
206 client.deleteSnapshot(request);
207 }
208
209 @Override
210 public void purgeAmi(String imageId) {
211 Image image = getImage(imageId);
212 List<String> snapshotIds = newArrayList();
213 List<BlockDeviceMapping> mappings = image.getBlockDeviceMappings();
214 for (BlockDeviceMapping mapping : mappings) {
215 Optional<EbsBlockDevice> ebsBlockDevice = fromNullable(mapping.getEbs());
216 if (ebsBlockDevice.isPresent()) {
217 snapshotIds.add(ebsBlockDevice.get().getSnapshotId());
218 }
219 }
220 DeregisterImageRequest request = new DeregisterImageRequest();
221 request.setImageId(imageId);
222 client.deregisterImage(request);
223 for (String snapshotId : snapshotIds) {
224 deleteSnapshot(snapshotId);
225 }
226 }
227
228 @Override
229 public Image createAmi(CreateAMIRequest request) {
230 Instance instance = getInstance(request.getInstanceId());
231 String rootVolumeId = getRootVolumeId(instance);
232 Snapshot snapshot = createSnapshot(rootVolumeId, request.getDescription(), request.getTimeoutMillis());
233 tag(snapshot.getSnapshotId(), request.getName());
234 return createAmi(request, instance, snapshot);
235 }
236
237 protected Image createAmi(CreateAMIRequest create, Instance instance, Snapshot snapshot) {
238 BlockDeviceMapping rootVolumeMapping = getRootVolumeMapping(instance, snapshot.getSnapshotId(), create.getRootVolume());
239 List<BlockDeviceMapping> mappings = newArrayList();
240 mappings.add(rootVolumeMapping);
241 for (BlockDeviceMapping mapping : create.getAdditionalMappings()) {
242 mappings.add(mapping);
243 }
244
245 RegisterImageRequest request = new RegisterImageRequest();
246 request.setName(create.getName().getValue());
247 request.setDescription(create.getDescription());
248 request.setArchitecture(instance.getArchitecture());
249 request.setRootDeviceName(instance.getRootDeviceName());
250 request.setKernelId(instance.getKernelId());
251 request.setBlockDeviceMappings(mappings);
252 RegisterImageResult result = client.registerImage(request);
253 String imageId = result.getImageId();
254 waitForAmiState(imageId, AMI_AVAILABLE_STATE, create.getTimeoutMillis());
255 tag(imageId, create.getName());
256 return getImage(imageId);
257 }
258
259 protected BlockDeviceMapping getRootVolumeMapping(Instance instance, String snapshotId, RootVolume rootVolume) {
260 InstanceBlockDeviceMapping ibdm = getRootVolumeMapping(instance);
261
262 EbsBlockDevice ebs = new EbsBlockDevice();
263 ebs.setDeleteOnTermination(rootVolume.getDeleteOnTermination().orNull());
264 ebs.setSnapshotId(snapshotId);
265 ebs.setVolumeSize(rootVolume.getSizeInGigabytes().orNull());
266
267 BlockDeviceMapping bdm = new BlockDeviceMapping();
268 bdm.setDeviceName(ibdm.getDeviceName());
269 bdm.setEbs(ebs);
270 return bdm;
271 }
272
273 protected BlockDeviceMapping convert(InstanceBlockDeviceMapping mapping, String snapshotId, int sizeInGigabytes) {
274 BlockDeviceMapping converted = new BlockDeviceMapping();
275 converted.setDeviceName(mapping.getDeviceName());
276 converted.setEbs(convert(mapping.getEbs(), snapshotId, sizeInGigabytes));
277 return converted;
278 }
279
280 protected EbsBlockDevice convert(EbsInstanceBlockDevice device, String snapshotId, int sizeInGigabytes) {
281 EbsBlockDevice converted = new EbsBlockDevice();
282 converted.setDeleteOnTermination(device.getDeleteOnTermination());
283 converted.setSnapshotId(snapshotId);
284 converted.setVolumeSize(sizeInGigabytes);
285 return converted;
286 }
287
288 protected Optional<Tag> getTag(List<Tag> tags, String key) {
289 for (Tag tag : tags) {
290 if (tag.getKey().equals(key)) {
291 return Optional.of(tag);
292 }
293 }
294 return absent();
295 }
296
297 protected String getRootVolumeId(Instance instance) {
298 checkNotNull(instance, "instance");
299 InstanceBlockDeviceMapping rootMapping = getRootVolumeMapping(instance);
300 return rootMapping.getEbs().getVolumeId();
301 }
302
303 protected InstanceBlockDeviceMapping getRootVolumeMapping(Instance instance) {
304 checkNotNull(instance, "instance");
305 String rootDeviceName = instance.getRootDeviceName();
306 List<InstanceBlockDeviceMapping> mappings = instance.getBlockDeviceMappings();
307 for (InstanceBlockDeviceMapping mapping : mappings) {
308 if (rootDeviceName.equals(mapping.getDeviceName())) {
309 return mapping;
310 }
311 }
312 throw illegalState("Unable to locate the root volume mapping for [%s]", instance.getInstanceId());
313 }
314
315 @Override
316 public Snapshot createSnapshot(String volumeId, String description, int timeoutMillis) {
317 CreateSnapshotRequest request = new CreateSnapshotRequest(volumeId, description);
318 CreateSnapshotResult result = client.createSnapshot(request);
319 waitForSnapshotState(result.getSnapshot().getSnapshotId(), SNAPSHOT_COMPLETED_STATE, timeoutMillis);
320 return result.getSnapshot();
321 }
322
323 @Override
324 public Snapshot getSnapshot(String snapshotId) {
325 DescribeSnapshotsRequest request = new DescribeSnapshotsRequest();
326 request.setSnapshotIds(singletonList(snapshotId));
327 DescribeSnapshotsResult result = client.describeSnapshots(request);
328 List<Snapshot> snapshots = result.getSnapshots();
329 checkState(snapshots.size() == 1, "expected 1 snapshot, but there were %s instead", snapshots.size());
330 return snapshots.get(0);
331 }
332
333 protected void waitForSnapshotState(String snapshotId, String state, int timeoutMillis) {
334 Condition condition = new SnapshotStateCondition(this, snapshotId, state);
335 WaitContext waitContext = getWaitContext(timeoutMillis);
336 Object[] args = { FormatUtils.getTime(waitContext.getTimeoutMillis()), snapshotId, state };
337 logger.info(format("waiting up to %s for snapshot [%s] to reach state '%s'", args));
338 WaitResult result = service.wait(waitContext, condition);
339 Object[] resultArgs = { snapshotId, state, FormatUtils.getTime(result.getElapsed()) };
340 logger.info(format("snapshot [%s] is now '%s' - %s", resultArgs));
341 }
342
343 protected void waitForAmiState(String imageId, String state, int timeoutMillis) {
344 Condition condition = new AmiStateCondition(this, imageId, state);
345 WaitContext waitContext = getWaitContext(timeoutMillis);
346 Object[] args = { FormatUtils.getTime(waitContext.getTimeoutMillis()), imageId, state };
347 logger.info(format("waiting up to %s for image [%s] to reach state '%s'", args));
348 WaitResult result = service.wait(waitContext, condition);
349 Object[] resultArgs = { imageId, state, FormatUtils.getTime(result.getElapsed()) };
350 logger.info(format("ami [%s] is now '%s' - %s", resultArgs));
351 }
352
353 @Override
354 public List<Image> getMyImages() {
355 DescribeImagesRequest request = new DescribeImagesRequest();
356 request.withOwners(AmiOwner.SELF.getValue());
357 DescribeImagesResult result = client.describeImages(request);
358 return result.getImages();
359 }
360
361 protected void checkSizeEquals(Collection<?> c, int size) {
362 checkState(c.size() == size, "expected size of %s, was %s instead", size, c.size());
363 }
364
365 @Override
366 public Image getImage(String imageId) {
367 DescribeImagesRequest request = new DescribeImagesRequest();
368 request.setImageIds(singletonList(imageId));
369 DescribeImagesResult result = client.describeImages(request);
370 List<Image> images = result.getImages();
371 checkSizeEquals(images, 1);
372 return images.get(0);
373 }
374
375 @Override
376 public String importKey(String keyName, String publicKey) {
377 checkNotBlank(keyName, "keyName");
378 checkNotBlank(publicKey, "publicKey");
379 ImportKeyPairRequest request = new ImportKeyPairRequest(keyName, publicKey);
380 ImportKeyPairResult result = client.importKeyPair(request);
381 return result.getKeyFingerprint();
382 }
383
384 @Override
385 public boolean isExistingKey(String keyName) {
386 checkNotBlank(keyName, "keyName");
387 DescribeKeyPairsResult result = client.describeKeyPairs();
388 List<KeyPairInfo> keys = result.getKeyPairs();
389 Optional<KeyPairInfo> optional = getKeyPairInfo(keyName, keys);
390 return optional.isPresent();
391 }
392
393 protected Optional<KeyPairInfo> getKeyPairInfo(String name, List<KeyPairInfo> list) {
394 for (KeyPairInfo element : list) {
395 logger.debug("fingerprint - {}, name - {}", element.getKeyFingerprint(), element.getKeyName());
396 if (name.equals(element.getKeyName())) {
397 return Optional.of(element);
398 }
399 }
400 return Optional.<KeyPairInfo> absent();
401 }
402
403 @Override
404 public List<String> getSecurityGroupNames() {
405 DescribeSecurityGroupsResult result = client.describeSecurityGroups();
406 List<String> names = newArrayList();
407 for (SecurityGroup group : result.getSecurityGroups()) {
408 names.add(group.getGroupName());
409 }
410 Collections.sort(names);
411 return ImmutableList.copyOf(names);
412 }
413
414 @Override
415 public void createSecurityGroup(KualiSecurityGroup group) {
416 checkNotNull(group, "group");
417 CreateSecurityGroupRequest request = new CreateSecurityGroupRequest();
418 request.setGroupName(group.getName());
419 if (group.getDescription().isPresent()) {
420 request.setDescription(group.getDescription().get());
421 }
422 client.createSecurityGroup(request);
423 setPermissions(group.getName(), group.getPermissions());
424 }
425
426 @Override
427 public boolean isExistingSecurityGroup(String name) {
428 checkNotBlank(name, "name");
429 return getSecurityGroup(name).isPresent();
430 }
431
432 @Override
433 public SetPermissionsResult setPermissions(String securityGroupName, List<Permission> permissions) {
434 checkNotBlank(securityGroupName, "securityGroupName");
435 checkNotNull(permissions, "permissions");
436 Optional<SecurityGroup> optional = getSecurityGroup(securityGroupName);
437 checkState(optional.isPresent(), "Security group [%s] does not exist", securityGroupName);
438 SecurityGroup group = optional.get();
439 List<IpPermission> oldPerms = group.getIpPermissions();
440 List<Permission> oldPermissions = getPermissions(oldPerms);
441
442 Set<Permission> newSet = new HashSet<Permission>(permissions);
443 Set<Permission> oldSet = new HashSet<Permission>(oldPermissions);
444
445 Set<Permission> adds = SetUtils.difference(newSet, oldSet);
446 Set<Permission> deletes = SetUtils.difference(oldSet, newSet);
447 Set<Permission> existing = SetUtils.intersection(newSet, oldSet);
448
449
450 if (deletes.size() > 0) {
451 RevokeSecurityGroupIngressRequest revoker = new RevokeSecurityGroupIngressRequest(securityGroupName, getIpPermissions(deletes));
452 client.revokeSecurityGroupIngress(revoker);
453 }
454
455
456 if (adds.size() > 0) {
457 AuthorizeSecurityGroupIngressRequest authorizer = new AuthorizeSecurityGroupIngressRequest();
458 authorizer.withGroupName(securityGroupName).withIpPermissions(getIpPermissions(adds));
459 client.authorizeSecurityGroupIngress(authorizer);
460 }
461
462 return new SetPermissionsResult(adds, deletes, existing);
463 }
464
465 @Override
466 public Optional<SecurityGroup> getSecurityGroup(String name) {
467 checkNotBlank(name, "name");
468 List<String> names = getSecurityGroupNames();
469 if (names.contains(name)) {
470 DescribeSecurityGroupsRequest request = new DescribeSecurityGroupsRequest();
471 request.setGroupNames(Collections.singletonList(name));
472 DescribeSecurityGroupsResult result = client.describeSecurityGroups(request);
473 List<SecurityGroup> groups = result.getSecurityGroups();
474 checkState(groups.size() == 1, "Expected exactly 1 security group but there were %s instead", groups.size());
475 SecurityGroup group = groups.get(0);
476 return Optional.of(group);
477 } else {
478 return Optional.<SecurityGroup> absent();
479 }
480 }
481
482 protected List<IpPermission> getIpPermissions(Collection<Permission> permissions) {
483 List<IpPermission> newPerms = new ArrayList<IpPermission>();
484 for (Permission perm : permissions) {
485 IpPermission newPerm = getIpPermission(perm);
486 newPerms.add(newPerm);
487 }
488 return ImmutableList.copyOf(newPerms);
489 }
490
491 protected List<Permission> getPermissions(Collection<IpPermission> permissions) {
492 List<Permission> newPerms = new ArrayList<Permission>();
493 for (IpPermission perm : permissions) {
494 Permission newPerm = getPermission(perm);
495 newPerms.add(newPerm);
496 }
497 return ImmutableList.copyOf(newPerms);
498 }
499
500 protected Permission getPermission(IpPermission perm) {
501 checkArgument(CollectionUtils.isEmpty(perm.getUserIdGroupPairs()), "User id / group pairs are not supported");
502 String protocolName = perm.getIpProtocol();
503 Integer fromPort = perm.getFromPort();
504 Integer toPort = perm.getToPort();
505 List<String> ipRanges = perm.getIpRanges();
506 Assert.noNulls(fromPort, toPort, ipRanges);
507 Assert.noBlanks(protocolName);
508 Assert.isTrue(fromPort.equals(toPort), "port ranges are not supported");
509 Protocol protocol = Protocol.valueOf(protocolName.toUpperCase());
510 return Permission.builder(fromPort).withCidrNotations(ipRanges).withProtocol(protocol).build();
511 }
512
513 protected IpPermission getIpPermission(Permission perm) {
514 IpPermission newPerm = new IpPermission();
515 newPerm.withIpRanges(perm.getCidrNotations());
516 newPerm.withIpProtocol(perm.getProtocol().getValue());
517 newPerm.withFromPort(perm.getPort());
518 newPerm.withToPort(perm.getPort());
519 return newPerm;
520 }
521
522 @Override
523 public Instance getInstance(String instanceId) {
524 checkNotBlank(instanceId, "instanceId");
525 List<Instance> instances = getInstances(ImmutableList.of(instanceId));
526 checkState(instances.size() == 1, "Expected exactly 1 instance but there were %s instead", instances.size());
527 return instances.get(0);
528 }
529
530 @Override
531 public List<Instance> getInstances(List<String> instances) {
532 checkNotNull(instances, "'instances' cannot be null");
533 DescribeInstancesRequest request = new DescribeInstancesRequest();
534 if (!instances.isEmpty()) {
535 request.setInstanceIds(instances);
536 }
537 DescribeInstancesResult result = client.describeInstances(request);
538 List<Instance> list = Lists.newArrayList();
539 List<Reservation> reservations = result.getReservations();
540 for (Reservation reservation : reservations) {
541 list.addAll(reservation.getInstances());
542 }
543 return ImmutableList.copyOf(list);
544 }
545
546 @Override
547 public List<Instance> getInstances() {
548 return getInstances(ImmutableList.<String> of());
549 }
550
551 @Override
552 public Instance launchInstance(LaunchInstanceContext context) {
553 checkNotNull(context, "context");
554
555
556 Instance instance = issueRunInstanceRequest(context);
557
558
559
560 sleep(this.context.getInitialPauseMillis());
561
562
563 tag(instance.getInstanceId(), context.getTags());
564
565
566 waitForOnlineConfirmation(instance, context);
567
568
569 return getInstance(instance.getInstanceId());
570 }
571
572 @Override
573 public void allowTermination(String instanceId) {
574 Assert.noBlanks(instanceId);
575 preventTermination(instanceId, false);
576 }
577
578 @Override
579 public void preventTermination(String instanceId) {
580 Assert.noBlanks(instanceId);
581 preventTermination(instanceId, true);
582 }
583
584 @Override
585 public Instance startInstance(String instanceId) {
586 checkNotBlank(instanceId, "instanceId");
587 StartInstancesRequest request = new StartInstancesRequest();
588 request.setInstanceIds(singletonList(instanceId));
589 client.startInstances(request);
590 WaitContext waitContext = getWaitContext(context.getTerminationTimeoutMillis());
591 Object[] args = { FormatUtils.getTime(waitContext.getTimeoutMillis()), instanceId, RUNNING.getValue() };
592 logger.info("Waiting up to {} for [{}] to start", args);
593 Condition online = new IsOnlineCondition(this, instanceId);
594 WaitResult result = service.wait(waitContext, online);
595 Object[] resultArgs = { instanceId, getTime(result.getElapsed()) };
596 logger.info("[{}] has been started - {}", resultArgs);
597 return getInstance(instanceId);
598 }
599
600 @Override
601 public void stopInstance(String instanceId) {
602 checkNotBlank(instanceId, "instanceId");
603 StopInstancesRequest request = new StopInstancesRequest();
604 request.setInstanceIds(singletonList(instanceId));
605 client.stopInstances(request);
606 WaitContext waitContext = getWaitContext(context.getTerminationTimeoutMillis());
607 Object[] args = { FormatUtils.getTime(waitContext.getTimeoutMillis()), instanceId, STOPPED.getValue() };
608 logger.info("Waiting up to {} for [{}] to stop", args);
609 Condition condition = new InstanceStateCondition(this, instanceId, STOPPED);
610 WaitResult result = service.wait(waitContext, condition);
611 Object[] resultArgs = { instanceId, getTime(result.getElapsed()) };
612 logger.info("[{}] has been stopped - {}", resultArgs);
613 }
614
615 @Override
616 public void terminateInstance(String instanceId) {
617 checkNotBlank(instanceId, "instanceId");
618 TerminateInstancesRequest request = new TerminateInstancesRequest();
619 request.setInstanceIds(singletonList(instanceId));
620 client.terminateInstances(request);
621 WaitContext waitContext = getWaitContext(context.getTerminationTimeoutMillis());
622 Object[] args = { FormatUtils.getTime(waitContext.getTimeoutMillis()), instanceId, TERMINATED.getValue() };
623 logger.info("Waiting up to {} for [{}] to terminate", args);
624 Condition condition = new InstanceStateCondition(this, instanceId, TERMINATED);
625 WaitResult result = service.wait(waitContext, condition);
626 Object[] resultArgs = { instanceId, getTime(result.getElapsed()) };
627 logger.info("[{}] has been terminated - {}", resultArgs);
628 }
629
630 @Override
631 public void tag(String resourceId, List<Tag> tags) {
632 checkNotBlank(resourceId, "resourceId");
633 checkNotNull(tags, "tags");
634 if (CollectionUtils.isEmpty(tags)) {
635 return;
636 }
637 List<String> resources = Collections.singletonList(resourceId);
638 CreateTagsRequest request = new CreateTagsRequest(resources, tags);
639 client.createTags(request);
640 }
641
642 @Override
643 public void tag(String resourceId, Tag tag) {
644 tag(resourceId, ImmutableList.of(tag));
645 }
646
647 @Override
648 public boolean isOnline(String instanceId) {
649 return new IsOnlineCondition(this, instanceId).isTrue();
650 }
651
652 @Override
653 public String getStatus(String instanceId, InstanceStatusType type, String statusName) {
654 List<InstanceStatus> statuses = getStatusList(instanceId);
655 return getStatus(statuses, type, statusName);
656 }
657
658 protected void preventTermination(String instanceId, boolean preventTermination) {
659 Assert.noBlanks(instanceId);
660 ModifyInstanceAttributeRequest request = new ModifyInstanceAttributeRequest();
661 request.withInstanceId(instanceId);
662
663
664
665 request.withDisableApiTermination(preventTermination);
666
667 client.modifyInstanceAttribute(request);
668 }
669
670 protected List<InstanceStatus> getStatusList(String instanceId) {
671 DescribeInstanceStatusRequest request = new DescribeInstanceStatusRequest();
672 request.setInstanceIds(Collections.singletonList(instanceId));
673 DescribeInstanceStatusResult result = client.describeInstanceStatus(request);
674 return result.getInstanceStatuses();
675 }
676
677 protected String getStatus(List<InstanceStatus> statuses, InstanceStatusType type, String name) {
678 for (InstanceStatus status : statuses) {
679 InstanceStatusSummary summary = getSummary(status, type);
680 Optional<String> detail = getStatusDetail(summary, name);
681 if (detail.isPresent()) {
682 return detail.get();
683 }
684 }
685 return InstanceStatusValue.UNKNOWN.getValue();
686 }
687
688 protected InstanceStatusSummary getSummary(InstanceStatus status, InstanceStatusType type) {
689 switch (type) {
690 case INSTANCE:
691 return status.getInstanceStatus();
692 case SYSTEM:
693 return status.getSystemStatus();
694 default:
695 throw new IllegalArgumentException("[" + type + "] is unknown");
696 }
697 }
698
699 protected Optional<String> getStatusDetail(InstanceStatusSummary summary, String name) {
700 List<InstanceStatusDetails> details = summary.getDetails();
701 for (InstanceStatusDetails detail : details) {
702 if (name.equals(detail.getName())) {
703 return Optional.of(detail.getStatus());
704 }
705 }
706 return Optional.absent();
707 }
708
709 protected Instance issueRunInstanceRequest(LaunchInstanceContext context) {
710 PublicKey publicKey = context.getPublicKey();
711 if (!isExistingKey(publicKey.getName())) {
712 logger.info("Importing key [{}]", publicKey.getName());
713 importKey(publicKey.getName(), publicKey.getValue());
714 }
715
716 List<String> securityGroupNames = getSecurityGroupNames();
717 for (KualiSecurityGroup securityGroup : context.getSecurityGroups()) {
718 if (!securityGroupNames.contains(securityGroup.getName())) {
719 logger.info("Creating security group {}", securityGroup.getName());
720 createSecurityGroup(securityGroup);
721 }
722 if (context.isOverrideExistingSecurityGroupPermissions()) {
723 SetPermissionsResult result = setPermissions(securityGroup.getName(), securityGroup.getPermissions());
724 logPermissionChanges(securityGroup, result.getDeletes(), "deleted");
725 logPermissionChanges(securityGroup, result.getAdds(), "added");
726 }
727 }
728
729 RunInstancesRequest request = getRunInstanceRequest(context);
730 RunInstancesResult result = client.runInstances(request);
731 Reservation r = result.getReservation();
732 List<Instance> instances = r.getInstances();
733 checkState(instances.size() == 1, "Expected exactly 1 instance but there were %s instead", instances.size());
734 return instances.get(0);
735 }
736
737 protected void logPermissionChanges(KualiSecurityGroup group, List<Permission> perms, String changeDescription) {
738 for (Permission perm : perms) {
739 String port = StringUtils.leftPad(perm.getPort() + "", 5);
740 String permDescription = "port:" + port + ", protocol:" + perm.getProtocol() + ", CIDR:" + CollectionUtils.asCSV(perm.getCidrNotations());
741 Object[] args = { group.getName(), StringUtils.rightPad(changeDescription, 7, " "), permDescription };
742 logger.info("Security Group:[{}] - permission {} [{}]", args);
743 }
744 }
745
746 protected WaitContext getWaitContext(int timeout) {
747 int sleep = context.getSleepIntervalMillis();
748 int pause = context.getInitialPauseMillis();
749 return WaitContext.builder(timeout).sleepMillis(sleep).initialPauseMillis(pause).build();
750 }
751
752 protected void waitForOnlineConfirmation(Instance instance, LaunchInstanceContext context) {
753 InstanceStateName running = InstanceStateName.RUNNING;
754 WaitContext waitContext = getWaitContext(context.getTimeoutMillis());
755 Object[] args = { FormatUtils.getTime(waitContext.getTimeoutMillis()), instance.getInstanceId(), running.getValue() };
756 logger.info("Waiting up to {} for [{}] to come online", args);
757 Condition online = new IsOnlineCondition(this, instance.getInstanceId());
758 WaitResult result = service.wait(waitContext, online);
759 Object[] resultArgs = { instance.getInstanceId(), FormatUtils.getTime(result.getElapsed()) };
760 logger.info("[{}] is now online - {}", resultArgs);
761 }
762
763 protected List<String> getNames(List<KualiSecurityGroup> groups) {
764
765 List<String> names = new ArrayList<String>();
766 for (KualiSecurityGroup group : groups) {
767 names.add(group.getName());
768 }
769 Collections.sort(names);
770 return ImmutableList.copyOf(names);
771 }
772
773
774
775
776 protected RunInstancesRequest getRunInstanceRequest(LaunchInstanceContext context) {
777 RunInstancesRequest rir = new RunInstancesRequest();
778 rir.setMaxCount(1);
779 rir.setMinCount(1);
780 rir.setImageId(context.getAmi());
781 rir.setKeyName(context.getPublicKey().getName());
782 rir.setSecurityGroups(getNames(context.getSecurityGroups()));
783 rir.setInstanceType(context.getType());
784 rir.setDisableApiTermination(context.isPreventTermination());
785 rir.setEbsOptimized(context.isEbsOptimized());
786 rir.setMonitoring(context.isEnableMonitoring());
787
788
789 if (context.getAvailabilityZone().isPresent()) {
790 String zone = context.getAvailabilityZone().get();
791 Placement placement = new Placement(zone);
792 rir.setPlacement(placement);
793 }
794
795
796
797
798 List<BlockDeviceMapping> mappings = getUpdatedBlockDeviceMappings(context);
799
800
801 rir.setBlockDeviceMappings(mappings);
802 return rir;
803 }
804
805 protected List<BlockDeviceMapping> getUpdatedBlockDeviceMappings(LaunchInstanceContext context) {
806
807 Image ami = getAmi(context.getAmi());
808
809
810 if (context.getRootVolume().isPresent()) {
811 updateRootBlockDeviceMapping(ami, context.getRootVolume().get());
812 }
813
814
815 List<BlockDeviceMapping> mappings = newArrayList(ami.getBlockDeviceMappings());
816
817
818 for (BlockDeviceMapping additionalMapping : context.getAdditionalMappings()) {
819
820
821 Optional<BlockDeviceMapping> optional = findMatch(mappings, additionalMapping);
822
823
824 if (optional.isPresent()) {
825
826 BlockDeviceMapping existing = optional.get();
827 existing.setDeviceName(additionalMapping.getDeviceName());
828 existing.setEbs(additionalMapping.getEbs());
829 existing.setNoDevice(additionalMapping.getNoDevice());
830 existing.setVirtualName(additionalMapping.getVirtualName());
831 } else {
832
833 mappings.add(additionalMapping);
834 }
835 }
836
837
838 return mappings;
839 }
840
841 protected Optional<BlockDeviceMapping> findMatch(List<BlockDeviceMapping> mappings, BlockDeviceMapping mapping) {
842 for (BlockDeviceMapping element : mappings) {
843 if (element.getDeviceName().equals(mapping.getDeviceName())) {
844 return Optional.of(element);
845 }
846 }
847 return absent();
848 }
849
850 protected void updateRootBlockDeviceMapping(Image ami, RootVolume rootVolume) {
851
852
853 BlockDeviceMapping mapping = getRootBlockDeviceMapping(ami);
854
855
856 EbsBlockDevice device = mapping.getEbs();
857
858
859 if (rootVolume.getSizeInGigabytes().isPresent()) {
860 int sizeInGigabytes = rootVolume.getSizeInGigabytes().get();
861 device.setVolumeSize(sizeInGigabytes);
862 }
863
864
865 if (rootVolume.getDeleteOnTermination().isPresent()) {
866 boolean deleteOnTermination = rootVolume.getDeleteOnTermination().get();
867 device.setDeleteOnTermination(deleteOnTermination);
868 }
869
870 }
871
872 protected BlockDeviceMapping getRootBlockDeviceMapping(Image image) {
873 String rootDeviceName = image.getRootDeviceName();
874 List<BlockDeviceMapping> mappings = image.getBlockDeviceMappings();
875 for (BlockDeviceMapping mapping : mappings) {
876 String deviceName = mapping.getDeviceName();
877 if (rootDeviceName.equals(deviceName)) {
878 return mapping;
879 }
880 }
881 throw illegalState("Could not locate the root block device mapping for AMI [%s]", image.getImageId());
882 }
883
884 public Image getAmi(String ami) {
885 checkNotBlank(ami, "ami");
886 DescribeImagesRequest request = new DescribeImagesRequest();
887 request.setImageIds(singletonList(ami));
888 DescribeImagesResult result = client.describeImages(request);
889 List<Image> images = result.getImages();
890 checkState(images.size() == 1, "Expected exactly 1 image but there were %s instead", images.size());
891 return images.get(0);
892 }
893
894 public EC2ServiceContext getContext() {
895 return context;
896 }
897
898 }