/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.brooklyn.location.jclouds.networking;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import javax.annotation.Nullable;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.core.location.geo.LocalhostExternalIpLoader;
import org.apache.brooklyn.location.jclouds.JcloudsLocation;
import org.apache.brooklyn.location.jclouds.JcloudsLocationCustomizer;
import org.apache.brooklyn.location.jclouds.JcloudsMachineLocation;
import org.apache.brooklyn.location.jclouds.JcloudsSshMachineLocation;
import org.jclouds.aws.AWSResponseException;
import org.jclouds.compute.ComputeService;
import org.jclouds.compute.domain.SecurityGroup;
import org.jclouds.compute.domain.Template;
import org.jclouds.compute.extensions.SecurityGroupExtension;
import org.jclouds.domain.Location;
import org.jclouds.net.domain.IpPermission;
import org.jclouds.net.domain.IpProtocol;
import org.jclouds.providers.ProviderMetadata;
import org.jclouds.providers.Providers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.brooklyn.location.jclouds.BasicJcloudsLocationCustomizer;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.core.task.Tasks;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.net.Cidr;
import org.apache.brooklyn.util.time.Duration;
import com.google.common.annotations.Beta;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.base.Throwables;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.UncheckedExecutionException;
/**
* Configures custom security groups on Jclouds locations.
*
* @see SecurityGroupExtension is an optional extension to jclouds compute service. It allows the manipulation of
* {@link SecurityGroup}s.
*
* This customizer can be injected into {@link JcloudsLocation#obtainOnce} using
* It will be executed after the provisiioning of the {@link JcloudsMachineLocation} to apply app-specific
* customization related to the security groups.
*
* @since 0.7.0
*/
@Beta
public class JcloudsLocationSecurityGroupCustomizer extends BasicJcloudsLocationCustomizer {
private static final Logger LOG = LoggerFactory.getLogger(JcloudsLocationSecurityGroupCustomizer.class);
// Caches instances of JcloudsLocationSecurityGroupCustomizer by application IDs.
private static final LoadingCache<String, JcloudsLocationSecurityGroupCustomizer> CUSTOMISERS = CacheBuilder.newBuilder()
.build(new CacheLoader<String, JcloudsLocationSecurityGroupCustomizer>() {
@Override
public JcloudsLocationSecurityGroupCustomizer load(final String appContext) throws Exception {
return new JcloudsLocationSecurityGroupCustomizer(appContext);
}
});
/** Caches the base security group that should be shared between all instances in the same Jclouds location */
private final Cache<Location, SecurityGroup> sharedGroupCache = CacheBuilder.newBuilder().build();
/** Caches security groups unique to instances */
private final Cache<String, SecurityGroup> uniqueGroupCache = CacheBuilder.newBuilder().build();
/** The context for this location customizer. */
private final String applicationId;
/** The CIDR for addresses that may SSH to machines. */
private Supplier<Cidr> sshCidrSupplier;
/**
* A predicate indicating whether the customiser can retry a request to add a security group
* or a rule after an throwable is thrown.
*/
private Predicate<Exception> isExceptionRetryable = Predicates.alwaysFalse();
protected JcloudsLocationSecurityGroupCustomizer(String applicationId) {
// Would be better to restrict with something like LocalhostExternalIpCidrSupplier, but
// we risk making machines inaccessible from Brooklyn when HA fails over.
this(applicationId, Suppliers.ofInstance(new Cidr("0.0.0.0/0")));
}
protected JcloudsLocationSecurityGroupCustomizer(String applicationId, Supplier<Cidr> sshCidrSupplier) {
this.applicationId = applicationId;
this.sshCidrSupplier = sshCidrSupplier;
}
/**
* Gets the customizer for the given applicationId. Multiple calls to this method with the
* same application context will return the same JcloudsLocationSecurityGroupCustomizer instance.
* @param applicationId An identifier for the application the customizer is to be used for
* @return the unique customizer for the given context
*/
public static JcloudsLocationSecurityGroupCustomizer getInstance(String applicationId) {
return CUSTOMISERS.getUnchecked(applicationId);
}
/**
* Gets a customizer for the given entity's application. Multiple calls to this method with entities
* in the same application will return the same JcloudsLocationSecurityGroupCustomizer instance.
* @param entity The entity the customizer is to be used for
* @return the unique customizer for the entity's owning application
*/
public static JcloudsLocationSecurityGroupCustomizer getInstance(Entity entity) {
return getInstance(entity.getApplicationId());
}
/**
* @param predicate
* A predicate whose return value indicates whether a request to add a security group
* or permission may be retried after its input {@link Exception} was thrown.
* @return this
*/
public JcloudsLocationSecurityGroupCustomizer setRetryExceptionPredicate(Predicate<Exception> predicate) {
this.isExceptionRetryable = checkNotNull(predicate, "predicate");
return this;
}
/**
* @param cidrSupplier A supplier returning a CIDR for hosts that are allowed to SSH to locations.
*/
public JcloudsLocationSecurityGroupCustomizer setSshCidrSupplier(Supplier<Cidr> cidrSupplier) {
this.sshCidrSupplier = checkNotNull(cidrSupplier, "cidrSupplier");
return this;
}
/** @see #addPermissionsToLocation(JcloudsSshMachineLocation, java.lang.Iterable) */
public JcloudsLocationSecurityGroupCustomizer addPermissionsToLocation(final JcloudsMachineLocation location, IpPermission... permissions) {
addPermissionsToLocation(location, ImmutableList.copyOf(permissions));
return this;
}
/** @see #addPermissionsToLocation(JcloudsSshMachineLocation, java.lang.Iterable) */
public JcloudsLocationSecurityGroupCustomizer addPermissionsToLocation(final JcloudsMachineLocation location, SecurityGroupDefinition securityGroupDefinition) {
addPermissionsToLocation(location, securityGroupDefinition.getPermissions());
return this;
}
private SecurityGroup getSecurityGroup(final String nodeId, final SecurityGroupExtension securityApi, final String locationId) {
// Expect to have two security groups on the node: one shared between all nodes in the location,
// that is cached in sharedGroupCache, and one created by Jclouds that is unique to the node.
// Relies on customize having been called before. This should be safe because the arguments
// needed to call this method are not available until post-instance creation.
SecurityGroup machineUniqueSecurityGroup;
Tasks.setBlockingDetails("Loading unique security group for node: " + nodeId);
try {
machineUniqueSecurityGroup = uniqueGroupCache.get(nodeId, new Callable<SecurityGroup>() {
@Override public SecurityGroup call() throws Exception {
SecurityGroup sg = getUniqueSecurityGroupForNodeCachingSharedGroupIfPreviouslyUnknown(nodeId, locationId, securityApi);
if (sg == null) {
throw new IllegalStateException("Failed to find machine-unique group on node: " + nodeId);
}
return sg;
}
});
} catch (UncheckedExecutionException e) {
throw Throwables.propagate(new Exception(e.getCause()));
} catch (ExecutionException e) {
throw Throwables.propagate(new Exception(e.getCause()));
} finally {
Tasks.resetBlockingDetails();
}
return machineUniqueSecurityGroup;
}
/**
* Applies the given security group permissions to the given location.
* <p>
* Takes no action if the location's compute service does not have a security group extension.
* <p>
* The {@code synchronized} block is to serialize the permission changes, preventing race
* conditions in some clouds. If multiple customizations of the same group are done in parallel
* the changes may not be picked up by later customizations, meaning the same rule could possibly be
* added twice, which would fail. A finer grained mechanism would be preferable here, but
* we have no access to the information required, so this brute force serializing is required.
*
* @param location Location to gain permissions
* @param permissions The set of permissions to be applied to the location
*/
public JcloudsLocationSecurityGroupCustomizer addPermissionsToLocation(final JcloudsMachineLocation location, final Iterable<IpPermission> permissions) {
synchronized (JcloudsLocationSecurityGroupCustomizer.class) {
ComputeService computeService = location.getParent().getComputeService();
String nodeId = location.getNode().getId();
addPermissionsToLocation(permissions, nodeId, computeService);
return this;
}
}
/**
* Removes the given security group permissions from the given node with the given compute service.
* <p>
* Takes no action if the compute service does not have a security group extension.
* @param permissions The set of permissions to be removed from the location
* @param location Location to remove permissions from
*/
public void removePermissionsFromLocation(final JcloudsMachineLocation location, final Iterable<IpPermission> permissions) {
synchronized (JcloudsLocationSecurityGroupCustomizer.class) {
ComputeService computeService = location.getParent().getComputeService();
String nodeId = location.getNode().getId();
removePermissionsFromLocation(permissions, nodeId, computeService);
}
}
/**
* Removes the given security group permissions from the given node with the given compute service.
* <p>
* Takes no action if the compute service does not have a security group extension.
* @param permissions The set of permissions to be removed from the node
* @param nodeId The id of the node to update
* @param computeService The compute service to use to apply the changes
*/
@VisibleForTesting
void removePermissionsFromLocation(Iterable<IpPermission> permissions, final String nodeId, ComputeService computeService) {
if (!computeService.getSecurityGroupExtension().isPresent()) {
LOG.warn("Security group extension for {} absent; cannot update node {} with {}",
new Object[] {computeService, nodeId, permissions});
return;
}
final SecurityGroupExtension securityApi = computeService.getSecurityGroupExtension().get();
final String locationId = computeService.getContext().unwrap().getId();
SecurityGroup machineUniqueSecurityGroup = getSecurityGroup(nodeId, securityApi, locationId);
for (IpPermission permission : permissions) {
removePermission(permission, machineUniqueSecurityGroup, securityApi);
}
}
/**
* Applies the given security group permissions to the given node with the given compute service.
* <p>
* Takes no action if the compute service does not have a security group extension.
* @param permissions The set of permissions to be applied to the node
* @param nodeId The id of the node to update
* @param computeService The compute service to use to apply the changes
*/
@VisibleForTesting
void addPermissionsToLocation(Iterable<IpPermission> permissions, final String nodeId, ComputeService computeService) {
if (!computeService.getSecurityGroupExtension().isPresent()) {
LOG.warn("Security group extension for {} absent; cannot update node {} with {}",
new Object[] {computeService, nodeId, permissions});
return;
}
final SecurityGroupExtension securityApi = computeService.getSecurityGroupExtension().get();
final String locationId = computeService.getContext().unwrap().getId();
// Expect to have two security groups on the node: one shared between all nodes in the location,
// that is cached in sharedGroupCache, and one created by Jclouds that is unique to the node.
// Relies on customize having been called before. This should be safe because the arguments
// needed to call this method are not available until post-instance creation.
SecurityGroup machineUniqueSecurityGroup = getSecurityGroup(nodeId, securityApi, locationId);
MutableList<IpPermission> newPermissions = MutableList.copyOf(permissions);
Iterables.removeAll(newPermissions, machineUniqueSecurityGroup.getIpPermissions());
for (IpPermission permission : newPermissions) {
addPermission(permission, machineUniqueSecurityGroup, securityApi);
}
}
/**
* Loads the security groups attached to the node with the given ID and returns the group
* that is unique to the node, per the application context. This method will also update
* {@link #sharedGroupCache} if no mapping for the shared group's location previously
* existed (e.g. Brooklyn was restarted and rebound to an existing application).
*
* Notice that jclouds will attach 2 securityGroups to the node if the locationId is `aws-ec2` so it needs to
* look for the uniqueSecurityGroup rather than the shared securityGroup.
*
* @param nodeId The id of the node in question
* @param locationId The id of the location in question
* @param securityApi The API to use to list security groups
* @return the security group unique to the given node, or null if one could not be determined.
*/
private SecurityGroup getUniqueSecurityGroupForNodeCachingSharedGroupIfPreviouslyUnknown(String nodeId, String locationId, SecurityGroupExtension securityApi) {
Set<SecurityGroup> groupsOnNode = securityApi.listSecurityGroupsForNode(nodeId);
if(groupsOnNode == null || groupsOnNode.isEmpty()){
return null;
}
SecurityGroup unique;
if (locationId.equals("aws-ec2")) {
if (groupsOnNode.size() == 2) {
String expectedSharedName = getNameForSharedSecurityGroup();
Iterator<SecurityGroup> it = groupsOnNode.iterator();
SecurityGroup shared = it.next();
if (shared.getName().endsWith(expectedSharedName)) {
unique = it.next();
} else {
unique = shared;
shared = it.next();
}
if (!shared.getName().endsWith(expectedSharedName)) {
LOG.warn("Couldn't determine which security group is shared between instances in app {}. Expected={}, found={}",
new Object[]{ applicationId, expectedSharedName, groupsOnNode });
return null;
}
// Shared entry might be missing if Brooklyn has rebound to an application
SecurityGroup old = sharedGroupCache.asMap().putIfAbsent(shared.getLocation(), shared);
LOG.info("Loaded unique security group for node {} (in {}): {}",
new Object[]{nodeId, applicationId, unique});
if (old == null) {
LOG.info("Proactively set shared group for app {} to: {}", applicationId, shared);
}
return unique;
} else {
LOG.warn("Expected to find two security groups on node {} in app {} (one shared, one unique). Found {}: {}",
new Object[]{ nodeId, applicationId, groupsOnNode.size(), groupsOnNode });
}
}
return Iterables.getOnlyElement(groupsOnNode);
}
/**
* Replaces security groups configured on the given template with one that allows
* SSH access on port 22 and allows communication on all ports between machines in
* the same group. Security groups are reused when templates have equal
* {@link org.jclouds.compute.domain.Template#getLocation locations}.
* <p>
* This method is called by Brooklyn when obtaining machines, as part of the
* {@link JcloudsLocationCustomizer} contract. It
* should not be called from anywhere else.
*
* @param location The Brooklyn location that has called this method while obtaining a machine
* @param computeService The compute service being used by the location argument to provision a machine
* @param template The machine template created by the location argument
*/
@Override
public void customize(JcloudsLocation location, ComputeService computeService, Template template) {
if (!computeService.getSecurityGroupExtension().isPresent()) {
LOG.warn("Security group extension for {} absent; cannot configure security groups in context: {}", computeService, applicationId);
} else if (template.getLocation() == null) {
LOG.warn("No location has been set on {}; cannot configure security groups in context: {}", template, applicationId);
} else {
LOG.info("Configuring security groups on location {} in context {}", location, applicationId);
setSecurityGroupOnTemplate(location, template, computeService.getSecurityGroupExtension().get());
}
}
private void setSecurityGroupOnTemplate(final JcloudsLocation location, final Template template, final SecurityGroupExtension securityApi) {
SecurityGroup shared;
Tasks.setBlockingDetails("Loading security group shared by instances in " + template.getLocation() +
" in app " + applicationId);
try {
shared = sharedGroupCache.get(template.getLocation(), new Callable<SecurityGroup>() {
@Override public SecurityGroup call() throws Exception {
return getOrCreateSharedSecurityGroup(template.getLocation(), securityApi);
}
});
} catch (ExecutionException e) {
throw Throwables.propagate(new Exception(e.getCause()));
} finally {
Tasks.resetBlockingDetails();
}
Set<String> originalGroups = template.getOptions().getGroups();
template.getOptions().securityGroups(shared.getName());
if (!originalGroups.isEmpty()) {
LOG.info("Replaced configured security groups: configured={}, replaced with={}", originalGroups, template.getOptions().getGroups());
} else {
LOG.debug("Configured security groups at {} to: {}", location, template.getOptions().getGroups());
}
}
/**
* Loads the security group to be shared between nodes in the same application in the
* given Location. If no such security group exists it is created.
*
* @param location The location in which the security group will be found
* @param securityApi The API to use to list and create security groups
* @return the security group to share between instances in the given location in this application
*/
private SecurityGroup getOrCreateSharedSecurityGroup(Location location, SecurityGroupExtension securityApi) {
final String groupName = getNameForSharedSecurityGroup();
// Could sort-and-search if straight search is too expensive
Optional<SecurityGroup> shared = Iterables.tryFind(securityApi.listSecurityGroupsInLocation(location), new Predicate<SecurityGroup>() {
@Override
public boolean apply(final SecurityGroup input) {
// endsWith because Jclouds prepends 'jclouds#' to security group names.
return input.getName().endsWith(groupName);
}
});
if (shared.isPresent()) {
LOG.info("Found existing shared security group in {} for app {}: {}",
new Object[]{location, applicationId, groupName});
return shared.get();
} else {
LOG.info("Creating new shared security group in {} for app {}: {}",
new Object[]{location, applicationId, groupName});
return createBaseSecurityGroupInLocation(groupName, location, securityApi);
}
}
/**
* Creates a security group with rules to:
* <ul>
* <li>Allow SSH access on port 22 from the world</li>
* <li>Allow TCP, UDP and ICMP communication between machines in the same group</li>
* </ul>
*
* It needs to consider locationId as port ranges and groupId are cloud provider-dependent e.g openstack nova
* wants from 1-65535 while aws-ec2 accepts from 0-65535.
*
*
* @param groupName The name of the security group to create
* @param location The location in which the security group will be created
* @param securityApi The API to use to create the security group
*
* @return the created security group
*/
private SecurityGroup createBaseSecurityGroupInLocation(String groupName, Location location, SecurityGroupExtension securityApi) {
SecurityGroup group = addSecurityGroupInLocation(groupName, location, securityApi);
Set<String> openstackNovaIds = getJcloudsLocationIds("openstack-nova");
String groupId = group.getProviderId();
int fromPort = 0;
if (location.getParent() != null && Iterables.contains(openstackNovaIds, location.getParent().getId())) {
groupId = group.getId();
fromPort = 1;
}
// Note: For groupName to work with GCE we also need to tag the machines with the same ID.
// See sourceTags section at https://developers.google.com/compute/docs/networking#firewalls
IpPermission.Builder allWithinGroup = IpPermission.builder()
.groupId(groupId)
.fromPort(fromPort)
.toPort(65535);
addPermission(allWithinGroup.ipProtocol(IpProtocol.TCP).build(), group, securityApi);
addPermission(allWithinGroup.ipProtocol(IpProtocol.UDP).build(), group, securityApi);
addPermission(allWithinGroup.ipProtocol(IpProtocol.ICMP).fromPort(-1).toPort(-1).build(), group, securityApi);
IpPermission sshPermission = IpPermission.builder()
.fromPort(22)
.toPort(22)
.ipProtocol(IpProtocol.TCP)
.cidrBlock(getBrooklynCidrBlock())
.build();
addPermission(sshPermission, group, securityApi);
return group;
}
private Set<String> getJcloudsLocationIds(final String jcloudsApiId) {
Set<String> openstackNovaProviders = FluentIterable.from(Providers.all())
.filter(new Predicate<ProviderMetadata>() {
@Override
public boolean apply(ProviderMetadata providerMetadata) {
return providerMetadata.getApiMetadata().getId().equals(jcloudsApiId);
}
}).transform(new Function<ProviderMetadata, String>() {
@Nullable
@Override
public String apply(ProviderMetadata input) {
return input.getId();
}
}).toSet();
return new ImmutableSet.Builder<String>()
.addAll(openstackNovaProviders)
.add(jcloudsApiId)
.build();
}
protected SecurityGroup addSecurityGroupInLocation(final String groupName, final Location location, final SecurityGroupExtension securityApi) {
LOG.debug("Creating security group {} in {}", groupName, location);
Callable<SecurityGroup> callable = new Callable<SecurityGroup>() {
@Override
public SecurityGroup call() throws Exception {
return securityApi.createSecurityGroup(groupName, location);
}
};
return runOperationWithRetry(callable);
}
protected SecurityGroup addPermission(final IpPermission permission, final SecurityGroup group, final SecurityGroupExtension securityApi) {
LOG.debug("Adding permission to security group {}: {}", group.getName(), permission);
Callable<SecurityGroup> callable = new Callable<SecurityGroup>() {
@Override
public SecurityGroup call() throws Exception {
try {
return securityApi.addIpPermission(permission, group);
} catch (AWSResponseException e) {
if ("InvalidPermission.Duplicate".equals(e.getError().getCode())) {
// already exists
LOG.info("Permission already exists for security group; continuing (logging underlying exception at debug): permission="+permission+"; group="+group);
LOG.debug("Permission already exists for security group; continuing: permission="+permission+"; group="+group, e);
return null;
} else {
throw e;
}
} catch (Exception e) {
Exceptions.propagateIfFatal(e);
if (e.toString().contains("InvalidPermission.Duplicate")) {
// belt-and-braces, in case
// already exists
LOG.info("Permission already exists for security group; continuing (but unexpected exception type): permission="+permission+"; group="+group, e);
return null;
} else {
throw Exceptions.propagate(e);
}
}
}
};
return runOperationWithRetry(callable);
}
protected SecurityGroup removePermission(final IpPermission permission, final SecurityGroup group, final SecurityGroupExtension securityApi) {
LOG.debug("Removing permission from security group {}: {}", group.getName(), permission);
Callable<SecurityGroup> callable = new Callable<SecurityGroup>() {
@Override
public SecurityGroup call() throws Exception {
return securityApi.removeIpPermission(permission, group);
}
};
return runOperationWithRetry(callable);
}
/** @return the CIDR block used to configure Brooklyn's in security groups */
public String getBrooklynCidrBlock() {
return sshCidrSupplier.get().toString();
}
/**
* @return The name to be used by security groups that will be shared between machines
* in the same location for this instance's application context.
*/
@VisibleForTesting
String getNameForSharedSecurityGroup() {
return "brooklyn-" + applicationId.toLowerCase() + "-shared";
}
/**
* Invalidates all entries in {@link #sharedGroupCache} and {@link #uniqueGroupCache}.
* Use to simulate the effects of rebinding Brooklyn to a deployment.
*/
@VisibleForTesting
void clearSecurityGroupCaches() {
LOG.info("Clearing security group caches");
sharedGroupCache.invalidateAll();
uniqueGroupCache.invalidateAll();
}
/**
* Runs the given callable. Repeats until the operation succeeds or {@link #isExceptionRetryable} indicates
* that the request cannot be retried.
*/
protected <T> T runOperationWithRetry(Callable<T> operation) {
int backoff = 64;
Exception lastException = null;
for (int retries = 0; retries < 100; retries++) {
try {
return operation.call();
} catch (Exception e) {
lastException = e;
if (isExceptionRetryable.apply(e)) {
LOG.debug("Attempt #{} failed to add security group: {}", retries + 1, e.getMessage());
try {
Thread.sleep(backoff);
} catch (InterruptedException e1) {
throw Exceptions.propagate(e1);
}
backoff = backoff << 1;
} else {
break;
}
}
}
throw new RuntimeException("Unable to add security group rule; repeated errors from provider", lastException);
}
/**
* @return
* A predicate that is true if an exception contains an {@link org.jclouds.aws.AWSResponseException}
* whose error code is either <code>InvalidGroup.InUse</code>, <code>DependencyViolation</code> or
* <code>RequestLimitExceeded</code>.
*/
public static Predicate<Exception> newAwsExceptionRetryPredicate() {
return new AwsExceptionRetryPredicate();
}
private static class AwsExceptionRetryPredicate implements Predicate<Exception> {
// Error reference: http://docs.aws.amazon.com/AWSEC2/latest/APIReference/errors-overview.html
private static final Set<String> AWS_ERRORS_TO_RETRY = ImmutableSet.of(
"InvalidGroup.InUse", "DependencyViolation", "RequestLimitExceeded");
@Override
public boolean apply(Exception input) {
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
AWSResponseException exception = Exceptions.getFirstThrowableOfType(input, AWSResponseException.class);
if (exception != null) {
String code = exception.getError().getCode();
return AWS_ERRORS_TO_RETRY.contains(code);
}
return false;
}
}
/**
* A supplier of CIDRs that loads the external IP address of the localhost machine.
*/
private static class LocalhostExternalIpCidrSupplier implements Supplier<Cidr> {
private volatile Cidr cidr;
@Override
public Cidr get() {
Cidr local = cidr;
if (local == null) {
synchronized (this) {
local = cidr;
if (local == null) {
String externalIp = LocalhostExternalIpLoader.getLocalhostIpWithin(Duration.seconds(5));
cidr = local = new Cidr(externalIp + "/32");
}
}
}
return local;
}
}
}