Github user aledsage commented on a diff in the pull request:

    https://github.com/apache/brooklyn-server/pull/425#discussion_r88352921
  
    --- Diff: 
locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/networking/JcloudsLocationSecurityGroupEditor.java
 ---
    @@ -0,0 +1,303 @@
    +/*
    + * 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.ComputeService;
    +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.Iterator;
    +import java.util.Set;
    +import java.util.concurrent.Callable;
    +
    +/**
    + * Utility for manipulating security groups.
    + *
    + * For convenience, constructors take an argument of a {@link 
ComputeService} and extract from it the
    + * information needed to edit groups. However, not all compute services 
support the security group extension,
    + * so if the editor is being created for a compute service where it is not 
known in advance then users
    + * should query {@link #hasServiceSupport()} before calling the methods to 
edit security groups.
    + */
    +public class JcloudsLocationSecurityGroupEditor {
    +
    +    private static final Logger LOG = 
LoggerFactory.getLogger(JcloudsLocationSecurityGroupEditor.class);
    +    public static final java.lang.String JCLOUDS_PREFIX = "jclouds#";
    +
    +    private final Location location;
    +    private final ComputeService computeService;
    +    private final Optional<SecurityGroupExtension> securityApi;
    +    private final String locationId;
    +    private final Predicate<Exception> isExceptionRetryable;
    +
    +    /**
    +     * Constructor for editor that will retry operations upon exceptions.
    +     * @param location JClouds location where security groups will be 
managed.
    +     * @param computeService The JClouds compute service instance.
    +     * @param predicate A predicate indicating whether the customiser can 
retry a request (e.g. to add a security group
    +     * or a rule) if the attempted operation throws a Throwable.
    +     */
    +    public JcloudsLocationSecurityGroupEditor(Location location, 
ComputeService computeService, Predicate predicate) {
    +        this.location = location;
    +        this.computeService = computeService;
    +        this.locationId = 
this.computeService.getContext().unwrap().getId();
    +        this.securityApi = 
this.computeService.getSecurityGroupExtension(); // TODO surely check for 
isPresent else throw?
    +        this.isExceptionRetryable = predicate;
    +    }
    +
    +    /**
    +     * Constructor for editor that never retries requests if the attempted 
operation fails.
    +     * @param location JClouds location where security groups will be 
managed.
    +     * @param computeService The JClouds compute service instance.
    +     */
    +    public JcloudsLocationSecurityGroupEditor(Location location, 
ComputeService computeService) {
    +        this(location, computeService, Predicates.alwaysFalse());
    +    }
    +
    +
    +    /**
    +     * Flag to indicate whether the given compute service has support for 
security groups.
    +     */
    +    public boolean hasServiceSupport() {
    +        return securityApi.isPresent();
    +    }
    +
    +    /**
    +     * Get the location in which security groups will be created or 
searched.
    +     */
    +    public Location getLocation() {
    +        return location;
    +    }
    +
    +    /**
    +     * Get the location id from the compute service (e.g. "aws-ec2").
    +     */
    +    public String getLocationId() {
    +        return locationId;
    +    }
    +
    +    public Set<SecurityGroup> getSecurityGroupsForNode(final String 
nodeId) {
    +        return securityApi.get().listSecurityGroupsForNode(nodeId);
    +    }
    +
    +    /**
    +     * Create the security group. As we use jclouds, groups are created 
with names prefixed
    +     * with {@link #JCLOUDS_PREFIX}. 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.get().createSecurityGroup(name, 
location);
    +            }
    +        };
    +        return runOperationWithRetry(callable);
    +    }
    +
    +    /**
    +     * Removes a security group and its permissions.
    +     * @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> callable = new Callable<Boolean>() {
    +            @Override
    +            public Boolean call() throws Exception {
    +                return 
securityApi.get().removeSecurityGroup(group.getId());
    +            }
    +        };
    +        return runOperationWithRetry(callable);
    +    }
    +
    +    public Set<SecurityGroup> listSecurityGroupsForNode(final String 
nodeId) {
    +        return securityApi.get().listSecurityGroupsForNode(nodeId);
    +    }
    +
    +    public Iterable<SecurityGroup> findSecurityGroupsMatching(Predicate 
predicate) {
    +        final Set<SecurityGroup> locationGroups = 
securityApi.get().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}. 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 String query = name.startsWith(JCLOUDS_PREFIX) ? name : 
JCLOUDS_PREFIX + name;
    +        final Iterable<SecurityGroup> groupsMatching = 
findSecurityGroupsMatching(new Predicate<SecurityGroup>() {
    +            @Override
    +            public boolean apply(final SecurityGroup input) {
    +                return input.getName().equals(query);
    +            }
    +        });
    +        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);
    +        }
    +        // TODO any of the calls could return null because of duplicate 
record. Fetch the new SG state ourselves in this case
    +        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 permissions The new permissions
    +     * @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.get().addIpPermission(permission, 
group);
    +                } catch (Exception e) {
    +                    Exceptions.propagateIfFatal(e);
    +
    +                    if (isDuplicate(e)) {
    +                        return group;
    +                    }
    +
    +                    throw Exceptions.propagate(e);
    +                }
    +            }
    +        };
    +        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;
    +            }
    +        }
    +
    +        if (e.toString().contains("already exists")) {
    +            return true;
    +        }
    +
    +        return false;
    +    }
    +
    +
    +    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.get().removeIpPermission(permission, 
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;
    +        for (int retries = 0; retries < 100; retries++) { // TODO this 
will try for about 2.0e+21 years; maybe not try so hard?
    +            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);
    --- End diff --
    
    Can we use our `Repeater`? It is very configurable, and gives us logging 
for free.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastruct...@apache.org or file a JIRA ticket
with INFRA.
---

Reply via email to