Java tutorial
/* * 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 com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import org.apache.brooklyn.util.exceptions.Exceptions; import org.jclouds.aws.AWSResponseException; import org.jclouds.compute.domain.SecurityGroup; import org.jclouds.compute.extensions.SecurityGroupExtension; import org.jclouds.domain.Location; import org.jclouds.net.domain.IpPermission; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Set; import java.util.concurrent.Callable; import java.util.regex.Pattern; import static com.google.common.base.Preconditions.checkNotNull; /** * Utility for manipulating security groups. */ public class SecurityGroupEditor { private static final Logger LOG = LoggerFactory.getLogger(SecurityGroupEditor.class); public static final java.lang.String JCLOUDS_PREFIX_REGEX = "^jclouds[#-]"; private final Location location; private final SecurityGroupExtension securityApi; private final Predicate<Exception> isExceptionRetryable; /** * Constructor for editor that never retries requests if the attempted operation fails. * @param location JClouds location where security groups will be managed. * @param securityGroupExtension The JClouds security group extension from the compute service for this location. */ public SecurityGroupEditor(Location location, SecurityGroupExtension securityGroupExtension) { this.location = checkNotNull(location, "location"); this.securityApi = checkNotNull(securityGroupExtension, "securityGroupExtension"); this.isExceptionRetryable = Predicates.alwaysFalse(); } /** * Constructor for editor that may retry operations upon exceptions. * @deprecated since 0.10.0 Ideally it should be possible to determine a suitable predicate internally to * this class by instantiating a type appropriate to the underlying cloud. TODO investigate and implement. * * @param location JClouds location where security groups will be managed. * @param securityGroupExtension The JClouds security group extension from the compute service for this location. * @param isExceptionRetryable used to determine for an exception whether to retry the operation that failed. */ @Deprecated public SecurityGroupEditor(Location location, SecurityGroupExtension securityGroupExtension, Predicate<Exception> isExceptionRetryable) { this.location = checkNotNull(location, "location"); this.securityApi = checkNotNull(securityGroupExtension, "securityGroupExtension"); this.isExceptionRetryable = isExceptionRetryable; } /** * Get the location in which security groups will be created or searched. */ public Location getLocation() { return location; } public Set<SecurityGroup> getSecurityGroupsForNode(final String nodeId) { return securityApi.listSecurityGroupsForNode(nodeId); } /** * Create the security group. As we use jclouds, groups are created with names prefixed * with {@link #JCLOUDS_PREFIX_REGEX}. This method is idempotent. * @param name Name of the group to create * @return The created group. */ public SecurityGroup createSecurityGroup(final String name) { LOG.debug("Creating security group {} in {}", name, location); Callable<SecurityGroup> callable = new Callable<SecurityGroup>() { @Override public SecurityGroup call() throws Exception { return securityApi.createSecurityGroup(name, location); } @Override public String toString() { return "Create security group " + name; } }; return runOperationWithRetry(callable); } /** * Removes a security group and its permissions. * @param group The security group. * @return true if the group was found and removed. */ public boolean removeSecurityGroup(final SecurityGroup group) { LOG.debug("Removing security group {} in {}", group.getName(), location); Callable<Boolean> removeIt = new RemoveSecurityGroup(group.getId()); return runOperationWithRetry(removeIt); } /** * Removes a security group and its permissions. * @param groupId The jclouds id (provider id) of the group (including region code) * @return true if the group was found and removed. */ public boolean removeSecurityGroup(final String groupId) { LOG.debug("Removing security group {} in {}", groupId, location); Callable<Boolean> removeIt = new RemoveSecurityGroup(groupId); return runOperationWithRetry(removeIt); } public Set<SecurityGroup> listSecurityGroupsForNode(final String nodeId) { return securityApi.listSecurityGroupsForNode(nodeId); } public Iterable<SecurityGroup> findSecurityGroupsMatching(Predicate predicate) { final Set<SecurityGroup> locationGroups = securityApi.listSecurityGroupsInLocation(location); return Iterables.filter(locationGroups, predicate); } /** * @see #findSecurityGroupByName(String) */ public static class AmbiguousGroupName extends IllegalArgumentException { public AmbiguousGroupName(String s) { super(s); } } /** * Find a security group with the given name. As we use jclouds, groups are created with names prefixed * with {@link #JCLOUDS_PREFIX_REGEX}. For convenience this method accepts names either with or without * the prefix. * @param name Name of the group to find. * @return An optional of the group. * @throws AmbiguousGroupName in the unexpected case that the cloud returns more than one matching group. */ public Optional<SecurityGroup> findSecurityGroupByName(final String name) { final Iterable<SecurityGroup> groupsMatching = findSecurityGroupsMatching(new Predicate<SecurityGroup>() { final String rawName = name.replaceAll(JCLOUDS_PREFIX_REGEX, ""); @Override public boolean apply(final SecurityGroup input) { return input.getName().replaceAll(JCLOUDS_PREFIX_REGEX, "").equals(rawName); } }); final ImmutableList<SecurityGroup> matches = ImmutableList.copyOf(groupsMatching); if (matches.size() == 0) { return Optional.absent(); } else if (matches.size() == 1) { return Optional.of(matches.get(0)); } else { throw new AmbiguousGroupName("Unexpected result of multiple groups matching " + name); } } /** * Add permissions to the security group, using {@link #addPermission(SecurityGroup, IpPermission)}. * @param group The group to update * @param permissions The new permissions * @return The updated group with the added permissions. */ public SecurityGroup addPermissions(final SecurityGroup group, final Iterable<IpPermission> permissions) { SecurityGroup lastGroup = group; for (IpPermission permission : permissions) { lastGroup = addPermission(group, permission); } return lastGroup; } /** * Add a permission to the security group. This operation is idempotent (will return the group unmodified if the * permission already exists on it). * @param group The group to update * @param permission The new permission * @return The updated group with the added permissions. */ public SecurityGroup addPermission(final SecurityGroup group, final IpPermission permission) { 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 (Exception e) { Exceptions.propagateIfFatal(e); if (isDuplicate(e)) { return group; } throw Exceptions.propagate(e); } } @Override public String toString() { return "Add permission " + permission + " to security group " + group; } }; return runOperationWithRetry(callable); } @Deprecated // TODO improve this - shouldn't have AWS specifics in here private boolean isDuplicate(Exception e) { // Sometimes AWSResponseException is wrapped in an IllegalStateException AWSResponseException cause = Exceptions.getFirstThrowableOfType(e, AWSResponseException.class); if (cause != null) { if ("InvalidPermission.Duplicate".equals(cause.getError().getCode())) { return true; } } return e.toString().contains("already exists"); } public SecurityGroup removePermission(final SecurityGroup group, final IpPermission permission) { 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); } @Override public String toString() { return "Remove permission " + permission + " from security group " + group; } }; return runOperationWithRetry(callable); } public SecurityGroup removePermissions(SecurityGroup group, final Iterable<IpPermission> permissions) { for (IpPermission permission : permissions) { group = removePermission(group, permission); } return group; } /** * 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; LOG.debug("Running operation {}", operation); for (int retries = 0; retries < 12; retries++) { // 12 = keep trying for about 5 minutes try { return operation.call(); } catch (Exception e) { lastException = e; if (isExceptionRetryable.apply(e)) { LOG.debug("Attempt #{} failed to run operation, due to: {}", 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 run operation '" + operation + "'; repeated errors from provider", lastException); } @Override public String toString() { return "JcloudsLocationSecurityGroupEditor{" + "location=" + location + ", securityApi=" + securityApi + ", isExceptionRetryable=" + isExceptionRetryable + '}'; } private class RemoveSecurityGroup implements Callable<Boolean> { private String groupId; public RemoveSecurityGroup(final String groupId) { this.groupId = groupId; } @Override public Boolean call() throws Exception { return securityApi.removeSecurityGroup(groupId); } @Override public String toString() { return "Remove security group " + groupId; } } }