http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsWinRmMachineLocation.java
----------------------------------------------------------------------
diff --git 
a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsWinRmMachineLocation.java
 
b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsWinRmMachineLocation.java
new file mode 100644
index 0000000..753d772
--- /dev/null
+++ 
b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsWinRmMachineLocation.java
@@ -0,0 +1,154 @@
+/*
+ * 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;
+
+import static brooklyn.util.JavaGroovyEquivalents.groovyTruth;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.apache.brooklyn.location.basic.WinRmMachineLocation;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.Template;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.util.flags.SetFromFlag;
+import brooklyn.util.net.Networking;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Optional;
+import com.google.common.net.HostAndPort;
+
+public class JcloudsWinRmMachineLocation extends WinRmMachineLocation 
implements JcloudsMachineLocation {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(JcloudsWinRmMachineLocation.class);
+
+    @SetFromFlag
+    JcloudsLocation jcloudsParent;
+    
+    @SetFromFlag
+    NodeMetadata node;
+    
+    @SetFromFlag
+    Template template;
+
+    public JcloudsWinRmMachineLocation() {
+    }
+
+    @Override
+    public String toVerboseString() {
+        return Objects.toStringHelper(this).omitNullValues()
+                .add("id", getId()).add("name", getDisplayName())
+                .add("user", getUser())
+                .add("address", getAddress())
+                .add("port", getPort())
+                .add("node", getNode())
+                .add("jcloudsId", getJcloudsId())
+                .add("privateAddresses", node.getPrivateAddresses())
+                .add("publicAddresses", node.getPublicAddresses())
+                .add("parentLocation", getParent())
+                .add("osDetails", getOsDetails())
+                .toString();
+    }
+
+    @Override
+    public int getPort() {
+        return getConfig(WINRM_PORT);
+    }
+    
+    @Override
+    public NodeMetadata getNode() {
+        return node;
+    }
+    
+    @Override
+    public Template getTemplate() {
+        return template;
+    }
+    
+    @Override
+    public JcloudsLocation getParent() {
+        return jcloudsParent;
+    }
+    
+    @Override
+    public String getHostname() {
+        InetAddress address = getAddress();
+        return (address != null) ? address.getHostAddress() : null;
+    }
+    
+    @Override
+    public Set<String> getPublicAddresses() {
+        return node.getPublicAddresses();
+    }
+    
+    @Override
+    public Set<String> getPrivateAddresses() {
+        return node.getPrivateAddresses();
+    }
+
+    @Override
+    public String getSubnetHostname() {
+        // TODO: TEMP FIX: WAS:
+        // String publicHostname = jcloudsParent.getPublicHostname(node, 
Optional.<HostAndPort>absent(), config().getBag());
+        // but this causes a call to JcloudsUtil.getFirstReachableAddress, 
which searches for accessible SSH service.
+        // This workaround is good for public nodes but not private-subnet 
ones.
+        return getHostname();
+    }
+
+    @Override
+    public String getSubnetIp() {
+        Optional<String> privateAddress = getPrivateAddress();
+        if (privateAddress.isPresent()) {
+            return privateAddress.get();
+        }
+
+        String hostname = jcloudsParent.getPublicHostname(node, 
Optional.<HostAndPort>absent(), config().getBag());
+        if (hostname != null && !Networking.isValidIp4(hostname)) {
+            try {
+                return InetAddress.getByName(hostname).getHostAddress();
+            } catch (UnknownHostException e) {
+                LOG.debug("Cannot resolve IP for hostname {} of machine {} (so 
returning hostname): {}", new Object[] {hostname, this, e});
+            }
+        }
+        return hostname;
+    }
+
+    protected Optional<String> getPrivateAddress() {
+        if (groovyTruth(node.getPrivateAddresses())) {
+            Iterator<String> pi = node.getPrivateAddresses().iterator();
+            while (pi.hasNext()) {
+                String p = pi.next();
+                // disallow local only addresses
+                if (Networking.isLocalOnly(p)) continue;
+                // other things may be public or private, but either way, 
return it
+                return Optional.of(p);
+            }
+        }
+        return Optional.absent();
+    }
+    
+    @Override
+    public String getJcloudsId() {
+        return node.getId();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/SudoTtyFixingCustomizer.java
----------------------------------------------------------------------
diff --git 
a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/SudoTtyFixingCustomizer.java
 
b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/SudoTtyFixingCustomizer.java
new file mode 100644
index 0000000..d48a67c
--- /dev/null
+++ 
b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/SudoTtyFixingCustomizer.java
@@ -0,0 +1,58 @@
+/*
+ * 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;
+
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+import org.jclouds.compute.ComputeService;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+
+import brooklyn.util.task.DynamicTasks;
+import brooklyn.util.task.ssh.SshTasks;
+import brooklyn.util.task.ssh.SshTasks.OnFailingTask;
+
+/**
+ * Wraps Brooklyn's sudo-tty mitigations in a {@link 
JcloudsLocationCustomizer} for easy(-ish) consumption
+ * in YAML blueprints:
+ *
+ * <pre>
+ *   name: My App
+ *   brooklyn.config:
+ *     provisioning.properties:
+ *       customizerType: SudoTtyFixingCustomizer
+ *   services: ...
+ * </pre>
+ *
+ * <p>This class should be seen as a temporary workaround and might disappear 
completely if/when Brooklyn takes care of this automatically.
+ *
+ * <p>See
+ * <a 
href='http://unix.stackexchange.com/questions/122616/why-do-i-need-a-tty-to-run-sudo-if-i-can-sudo-without-a-password'>http://unix.stackexchange.com/questions/122616/why-do-i-need-a-tty-to-run-sudo-if-i-can-sudo-without-a-password</a>
+ * for background.
+ */
+@Beta
+public class SudoTtyFixingCustomizer extends BasicJcloudsLocationCustomizer {
+
+    @Override
+    public void customize(JcloudsLocation location, ComputeService 
computeService, JcloudsMachineLocation machine) {
+        Preconditions.checkArgument(machine instanceof SshMachineLocation, 
"machine must be SshMachineLocation, but is %s", machine.getClass());
+        
DynamicTasks.queueIfPossible(SshTasks.dontRequireTtyForSudo((SshMachineLocation)machine,
 OnFailingTask.FAIL)).orSubmitAndBlock();
+        DynamicTasks.waitForLast();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/networking/JcloudsLocationSecurityGroupCustomizer.java
----------------------------------------------------------------------
diff --git 
a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/networking/JcloudsLocationSecurityGroupCustomizer.java
 
b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/networking/JcloudsLocationSecurityGroupCustomizer.java
new file mode 100644
index 0000000..687a486
--- /dev/null
+++ 
b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/networking/JcloudsLocationSecurityGroupCustomizer.java
@@ -0,0 +1,563 @@
+/*
+ * 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.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 brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.net.Cidr;
+import brooklyn.util.task.Tasks;
+import 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;
+
+/**
+ * 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;
+    }
+
+    /**
+     * 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.
+     * @param permissions The set of permissions to be applied to the location
+     * @param location Location to gain permissions
+     */
+    public JcloudsLocationSecurityGroupCustomizer 
addPermissionsToLocation(final JcloudsMachineLocation location, final 
Iterable<IpPermission> permissions) {
+        ComputeService computeService = 
location.getParent().getComputeService();
+        String nodeId = location.getNode().getId();
+        addPermissionsToLocation(permissions, nodeId, computeService);
+        return this;
+    }
+
+    /**
+     * 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;
+        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 (ExecutionException e) {
+            throw Throwables.propagate(new Exception(e.getCause()));
+        } finally {
+            Tasks.resetBlockingDetails();
+        }
+        for (IpPermission permission : permissions) {
+            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);
+        SecurityGroup unique;
+        if (locationId.equals("aws-ec2")) {
+            if (groupsOnNode.size() != 2) {
+                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 null;
+            }
+            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;
+        }
+        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 {
+                return securityApi.addIpPermission(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;
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/networking/JcloudsPortForwarderExtension.java
----------------------------------------------------------------------
diff --git 
a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/networking/JcloudsPortForwarderExtension.java
 
b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/networking/JcloudsPortForwarderExtension.java
new file mode 100644
index 0000000..00fd7f8
--- /dev/null
+++ 
b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/networking/JcloudsPortForwarderExtension.java
@@ -0,0 +1,45 @@
+/*
+ * 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 org.jclouds.compute.domain.NodeMetadata;
+
+import org.apache.brooklyn.location.access.BrooklynAccessUtils;
+import org.apache.brooklyn.location.access.PortForwardManager;
+import brooklyn.util.net.Cidr;
+import brooklyn.util.net.Protocol;
+
+import com.google.common.base.Optional;
+import com.google.common.net.HostAndPort;
+
+public interface JcloudsPortForwarderExtension {
+
+    /**
+     * Opens port forwarding (e.g. DNAT or iptables port-forwarding) to reach 
the given given 
+     * target port on this node (from the given cidr).
+     * 
+     * This should also register the port with the {@link PortForwardManager}, 
via 
+     * {@code portForwardManager.associate(node.getId(), result, targetPort)} 
so that
+     * subsequent calls to {@link 
BrooklynAccessUtils#getBrooklynAccessibleAddress(brooklyn.entity.Entity, int)}
+     * will know about the mapped port.
+     */
+    public HostAndPort openPortForwarding(NodeMetadata node, int targetPort, 
Optional<Integer> optionalPublicPort, Protocol protocol, Cidr accessingCidr);
+
+    public void closePortForwarding(NodeMetadata node, int targetPort, 
HostAndPort publicHostAndPort, Protocol protocol);
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/networking/SecurityGroupDefinition.java
----------------------------------------------------------------------
diff --git 
a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/networking/SecurityGroupDefinition.java
 
b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/networking/SecurityGroupDefinition.java
new file mode 100644
index 0000000..8b95d1e
--- /dev/null
+++ 
b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/networking/SecurityGroupDefinition.java
@@ -0,0 +1,103 @@
+/*
+ * 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 java.util.List;
+import java.util.concurrent.Callable;
+
+import org.jclouds.aws.ec2.AWSEC2Api;
+import org.jclouds.compute.ComputeServiceContext;
+import org.jclouds.net.domain.IpPermission;
+import org.jclouds.net.domain.IpProtocol;
+import org.jclouds.net.util.IpPermissions;
+
+import brooklyn.util.collections.MutableList;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.net.Cidr;
+import brooklyn.util.text.Identifiers;
+
+import com.google.common.annotations.Beta;
+
+/** WIP to define a security group in an up-front way, where subsequently it 
can be applied to a jclouds location */
+@Beta
+public class SecurityGroupDefinition {
+
+    private Callable<String> groupNameFactory = new Callable<String>() { 
public String call() { return "br-sg-"+Identifiers.makeRandomId(8); } };
+    private List<IpPermission> ipPerms = MutableList.of();
+    
+    public void createGroupInAwsRegion(ComputeServiceContext 
computeServiceContext, String region) {
+        AWSEC2Api ec2Client = computeServiceContext.unwrapApi(AWSEC2Api.class);
+        String sgId = 
ec2Client.getSecurityGroupApi().get().createSecurityGroupInRegionAndReturnId(region,
 getName(), "Brooklyn-managed security group "+getName());
+        
ec2Client.getSecurityGroupApi().get().authorizeSecurityGroupIngressInRegion(region,
 sgId, ipPerms);
+    }
+
+    /** allows access to the given port on TCP from within the subnet */
+    public SecurityGroupDefinition allowingInternalPort(int port) {
+        return allowing(IpPermissions.permit(IpProtocol.TCP).port(port));
+    }
+    public SecurityGroupDefinition allowingInternalPorts(int port1, int port2, 
int ...ports) {
+        allowing(IpPermissions.permit(IpProtocol.TCP).port(port1));
+        allowing(IpPermissions.permit(IpProtocol.TCP).port(port2));
+        for (int port: ports)
+            allowing(IpPermissions.permit(IpProtocol.TCP).port(port));
+        return this;
+    }
+    public SecurityGroupDefinition allowingInternalPortRange(int 
portRangeStart, int portRangeEnd) {
+        return 
allowing(IpPermissions.permit(IpProtocol.TCP).fromPort(portRangeStart).to(portRangeEnd));
+    }
+    public SecurityGroupDefinition allowingInternalPing() {
+        return allowing(IpPermissions.permit(IpProtocol.ICMP));
+    }
+    
+    public SecurityGroupDefinition allowingPublicPort(int port) {
+        return 
allowing(IpPermissions.permit(IpProtocol.TCP).port(port).originatingFromCidrBlock(Cidr.UNIVERSAL.toString()));
+    }
+    public SecurityGroupDefinition allowingPublicPorts(int port1, int port2, 
int ...ports) {
+        
allowing(IpPermissions.permit(IpProtocol.TCP).port(port1).originatingFromCidrBlock(Cidr.UNIVERSAL.toString()));
+        
allowing(IpPermissions.permit(IpProtocol.TCP).port(port2).originatingFromCidrBlock(Cidr.UNIVERSAL.toString()));
+        for (int port: ports)
+            
allowing(IpPermissions.permit(IpProtocol.TCP).port(port).originatingFromCidrBlock(Cidr.UNIVERSAL.toString()));
+        return this;
+    }
+    public SecurityGroupDefinition allowingPublicPortRange(int portRangeStart, 
int portRangeEnd) {
+        return 
allowing(IpPermissions.permit(IpProtocol.TCP).fromPort(portRangeStart).to(portRangeEnd).originatingFromCidrBlock(Cidr.UNIVERSAL.toString()));
+    }
+    public SecurityGroupDefinition allowingPublicPing() {
+        return 
allowing(IpPermissions.permit(IpProtocol.ICMP).originatingFromCidrBlock(Cidr.UNIVERSAL.toString()));
+    }
+    
+    public SecurityGroupDefinition allowing(IpPermission permission) {
+        ipPerms.add(permission);
+        return this;
+    }
+    
+    // TODO use cloud machine namer
+    public SecurityGroupDefinition named(final String name) {
+        groupNameFactory = new Callable<String>() { public String call() { 
return name; } };
+        return this;
+    }
+    public String getName() { 
+        try { return groupNameFactory.call(); } 
+        catch (Exception e) { throw Exceptions.propagate(e); } 
+    }
+
+    public Iterable<IpPermission> getPermissions() {
+        return ipPerms;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/networking/SecurityGroupTool.java
----------------------------------------------------------------------
diff --git 
a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/networking/SecurityGroupTool.java
 
b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/networking/SecurityGroupTool.java
new file mode 100644
index 0000000..bf33f68
--- /dev/null
+++ 
b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/networking/SecurityGroupTool.java
@@ -0,0 +1,167 @@
+/*
+ * 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 java.util.Set;
+
+import org.apache.brooklyn.location.jclouds.JcloudsLocation;
+import org.apache.brooklyn.location.jclouds.JcloudsLocationConfig;
+import org.jclouds.aws.ec2.AWSEC2Api;
+import org.jclouds.aws.util.AWSUtils;
+import org.jclouds.compute.domain.SecurityGroup;
+import org.jclouds.compute.extensions.SecurityGroupExtension;
+import org.jclouds.net.domain.IpPermission;
+import org.jclouds.rest.ApiContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.text.Strings;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+
+/** WIP to apply a security group to a jclouds endpoint.
+ * <p>
+ * sections of this code have been used but overall it is not yet in a working 
state,
+ * but merged May 2014 to make it easier to pick up if and when needed.
+ * (if not needed after several months this may simply be removed.) */
+@Beta
+public class SecurityGroupTool {
+
+    private static final Logger log = 
LoggerFactory.getLogger(SecurityGroupTool.class);
+    
+    protected final JcloudsLocation location;
+    protected final SecurityGroupDefinition sgDef;
+
+    public SecurityGroupTool(JcloudsLocation location, SecurityGroupDefinition 
sgDef) {
+        this.location = Preconditions.checkNotNull(location);
+        this.sgDef = Preconditions.checkNotNull(sgDef);
+    }
+    
+    public String getName() {
+        return sgDef.getName();
+    }
+    
+    public void apply() {
+        Optional<SecurityGroupExtension> sgExtO = 
location.getComputeService().getSecurityGroupExtension();
+        if (!sgExtO.isPresent()) {
+            throw new IllegalStateException("Advanced networking not supported 
in this location ("+location+")");
+        }
+        SecurityGroupExtension sgExt = sgExtO.get();
+        
+        SecurityGroup sg = findSecurityGroupWithName(sgExt, getName());
+        if (sg==null) {
+            // TODO initialize the location
+            org.jclouds.domain.Location sgLoc = null;
+
+            // TODO record that we created it
+            // create it
+            try {
+                // FIXME this will always fail for providers which need a 
location, until we set it above
+                // 
https://github.com/brooklyncentral/brooklyn/pull/1343#discussion_r12275188
+                sg = sgExt.createSecurityGroup(getName(), sgLoc);
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                // check if someone else already created it
+                sg = findSecurityGroupWithName(sgExt, getName());
+                if (sg==null) {
+                    // no - so propagate error
+                    throw Exceptions.propagate(e);
+                } else {
+                    log.debug("Looks like parallel thread created security 
group "+getName()+"; ignoring error in our thread ("+e+") as we now have an 
SG");
+                }
+            }
+        }
+        
+        if (sg==null)
+            throw new IllegalStateException("Unable to find or create security 
group ID for "+getName());
+
+        addPermissions(sgExt, sg);
+    }
+    
+    protected SecurityGroup findSecurityGroupWithName(SecurityGroupExtension 
sgExt, String name) {
+        Set<SecurityGroup> groups = sgExt.listSecurityGroups();
+        // jclouds appends this sometimes so for portability let's add this
+        String nameAlt = name.startsWith("jclouds#") ? 
Strings.removeFromStart(name, "jclouds#") : "jclouds#"+name;
+        for (SecurityGroup g: groups) {
+            if (name.equals(g.getName())) return g;
+            if (nameAlt.equals(g.getName())) return g;
+        }
+        return null;
+    }
+
+    protected void addPermissions(SecurityGroupExtension sgExt, SecurityGroup 
sg) {
+
+        Object api = 
((ApiContext<?>)location.getComputeService().getContext().unwrap()).getApi();
+        if (api instanceof AWSEC2Api) {
+            // optimization for AWS where rules can be added all at once, and 
it cuts down Req Limit Exceeded problems!
+            String region = 
AWSUtils.getRegionFromLocationOrNull(sg.getLocation());
+            String id = sg.getProviderId();
+            
+            
((AWSEC2Api)api).getSecurityGroupApi().get().authorizeSecurityGroupIngressInRegion(region,
 id, sgDef.getPermissions());
+            
+        } else {
+            for (IpPermission p: sgDef.getPermissions()) {
+                sgExt.addIpPermission(p, sg);
+            }
+        }
+    }
+    
+    
+    // TODO remove this method once we've confirmed the above works nicely 
(this is an early attempt)
+    protected void applyOldEc2(AWSEC2Api client) {
+        String region = 
location.getConfig(JcloudsLocationConfig.CLOUD_REGION_ID);
+        if (region==null) {
+            // TODO where does the default come from?
+            log.warn("No region set for "+location+"; assuming EC2");
+            region = "us-east-1"; 
+        }
+        
+        Set<org.jclouds.ec2.domain.SecurityGroup> groups = 
client.getSecurityGroupApi().get().describeSecurityGroupsInRegion(region, 
getName());
+        String id = null;
+        if (groups.isEmpty()) {
+            // create it
+            try {
+                id = 
client.getSecurityGroupApi().get().createSecurityGroupInRegionAndReturnId(region
 , getName(), "Brooklyn-managed security group "+getName());
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                // check if someone else already created it!
+                groups = 
client.getSecurityGroupApi().get().describeSecurityGroupsInRegion(region, 
getName());
+                if (groups.isEmpty()) {
+                    // no - so propagate error
+                    throw Exceptions.propagate(e);
+                } else {
+                    log.debug("Looks like parallel thread created security 
group "+getName()+"; ignoring error in our thread ("+e+") as we now have an 
SG");
+                }
+            }
+        }
+        if (!groups.isEmpty()) {
+            if (groups.size()>1)
+                log.warn("Multiple security groups matching '"+getName()+"' 
(using the first): "+groups);
+            id = groups.iterator().next().getId();
+        }
+        if (id==null)
+            throw new IllegalStateException("Unable to find or create security 
group ID for "+getName());
+
+        
client.getSecurityGroupApi().get().authorizeSecurityGroupIngressInRegion(region,
 id, sgDef.getPermissions());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/pool/MachinePool.java
----------------------------------------------------------------------
diff --git 
a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/pool/MachinePool.java
 
b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/pool/MachinePool.java
new file mode 100644
index 0000000..79a3dc8
--- /dev/null
+++ 
b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/pool/MachinePool.java
@@ -0,0 +1,395 @@
+/*
+ * 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.pool;
+
+import static 
org.apache.brooklyn.location.jclouds.pool.MachinePoolPredicates.compose;
+import static 
org.apache.brooklyn.location.jclouds.pool.MachinePoolPredicates.matching;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.jclouds.compute.ComputeService;
+import org.jclouds.compute.RunNodesException;
+import org.jclouds.compute.domain.ComputeMetadata;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.Template;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+/**
+ * Contains details of machines detected at a given cloud (ComputeService),
+ * and records claims made against those machines via this pool.
+ * <p>
+ * Machine instances themselves are persisted and rescanned as new instances 
of this class are created.
+ * Claims however are specific to this instance of the class, i.e. <b>not</b> 
persisted.
+ * <p>
+ * This class is believed to be thread-safe.
+ * Refreshes to the remote detected machines are synchronized on the pool 
instance.
+ * Details of detected and claimed machines are also synchronized on the pool 
instance.
+ * (If it is necessary to claim machines whilst the pool is being rescanned,
+ * we can investigate a more sophisticated threading model.
+ * Access to some fields is clearly independent and uses a tighter 
synchonization
+ * strategy, e.g. templates.  
+ * Synchronization of fields within a synch block on the class instance
+ * is permitted, but not the other way round,
+ * and synching on multiple fields is also not permitted.)
+ * <p>
+ * Callers wishing to guarantee results of e.g. ensureUnclaimed remaining 
available
+ * can synchronize on this class for the duration that they wish to have that 
guarantee
+ * (at the cost, of course, of any other threads being able to access this 
pool).
+ * <p>
+ * If underlying provisioning/destroying operations fail, the pool
+ * currently may be in an unknown state, currently.
+ * If more robustness is needed this can be added.
+ * 
+ * @deprecated since 0.6.0; never used in production setting, and thus of 
dubious value; best avoided as unlikely to be supported in future versions
+ */
+@Deprecated
+public class MachinePool {
+    
+    private static final Logger log = 
LoggerFactory.getLogger(MachinePool.class);
+    
+    protected final ComputeService computeService;
+    final AtomicBoolean refreshNeeded = new AtomicBoolean(true);
+    final List<ReusableMachineTemplate> templates = new 
ArrayList<ReusableMachineTemplate>();
+    String poolName = null;
+    
+    /** all machines detected, less those in the black list */
+    volatile MachineSet detectedMachines = new MachineSet();
+    volatile MachineSet matchedMachines = new MachineSet();
+    volatile MachineSet claimedMachines = new MachineSet();
+    volatile MachineSet blacklistedMachines = new MachineSet();
+    
+    public MachinePool(ComputeService computeService) {
+        this.computeService = computeService;
+    }
+    
+    protected synchronized void init() {
+        if (!refreshNeeded.get()) return;
+        refresh();
+    }
+    
+    public void setPoolName(String poolName) {
+        if (poolName!=null)
+            log.warn("Changing pool name of "+this+" (from "+this.poolName+" 
to "+poolName+") is discouraged.");
+        this.poolName = poolName;
+    }
+    /** pool name is used as a group/label by jclouds, for convenience only; 
+     * it has no special properties for detecting matching instances
+     * (use explicit tags on the templates, for that). 
+     * defaults to name of pool class and user name.
+     * callers should set pool name before getting, if using a custom name. */
+    public synchronized String getPoolName() {
+        if (poolName==null)
+            poolName = 
getClass().getSimpleName()+"-"+System.getProperty("user.name");
+        return poolName;
+    }
+    
+    /** refreshes the pool of machines from the server (finding all instances 
matching the registered templates) */
+    public synchronized void refresh() {
+        refreshNeeded.set(false);
+        Set<? extends ComputeMetadata> computes = computeService.listNodes();
+        Set<NodeMetadata> nodes = new LinkedHashSet<NodeMetadata>();
+        for (ComputeMetadata c: computes) {
+            if (c instanceof NodeMetadata) {
+                nodes.add((NodeMetadata)c);
+            } else {
+                // TODO should we try to fetch more info?
+                log.warn("MachinePool "+this+" ignoring non-Node record for 
remote machine: "+c);
+            }
+        }
+
+        MachineSet allNewDetectedMachines = new MachineSet(nodes);
+        MachineSet newDetectedMachines = 
filterForAllowedMachines(allNewDetectedMachines);
+        MachineSet oldDetectedMachines = detectedMachines;
+        MachineSet newMatchedMachines = new MachineSet();
+        detectedMachines = newDetectedMachines;
+
+        MachineSet appearedMachinesIncludingBlacklist = 
allNewDetectedMachines.removed(oldDetectedMachines);
+        MachineSet appearedMachines = 
filterForAllowedMachines(appearedMachinesIncludingBlacklist);
+        if (appearedMachinesIncludingBlacklist.size()>appearedMachines.size())
+            if (log.isDebugEnabled()) log.debug("Pool "+this+", ignoring 
"+(appearedMachinesIncludingBlacklist.size()-appearedMachines.size())+" 
disallowed");
+        int matchedAppeared = 0;
+        for (NodeMetadata m: appearedMachines) {
+            if (m.getStatus() != NodeMetadata.Status.RUNNING) {
+                if (log.isDebugEnabled()) 
+                    log.debug("Pool "+this+", newly detected machine "+m+", 
not running ("+m.getStatus()+")");
+            } else {
+                Set<ReusableMachineTemplate> ts = 
getTemplatesMatchingInstance(m);
+                if (!ts.isEmpty()) {
+                    matchedAppeared++;
+                    newMatchedMachines = newMatchedMachines.added(new 
MachineSet(m));
+                    if (log.isDebugEnabled()) 
+                        log.debug("Pool "+this+", newly detected machine 
"+m+", matches pool templates "+ts);
+                } else {
+                    if (log.isDebugEnabled()) 
+                        log.debug("Pool "+this+", newly detected machine 
"+m+", does not match any pool templates");
+                }
+            }
+        }
+        if (matchedAppeared>0) {
+            log.info("Pool "+this+" discovered "+matchedAppeared+" matching 
machines (of "+appearedMachines.size()+" total new; 
"+newDetectedMachines.size()+" total including claimed and unmatched)");
+        } else {
+            if (log.isDebugEnabled()) 
+                log.debug("Pool "+this+" discovered "+matchedAppeared+" 
matching machines (of "+appearedMachines.size()+" total new; 
"+newDetectedMachines.size()+" total including claimed and unmatched)");
+        }
+        matchedMachines = newMatchedMachines;
+    }
+
+    protected MachineSet filterForAllowedMachines(MachineSet input) {
+        return input.removed(blacklistedMachines);
+    }
+
+    // TODO template registry and claiming from a template could be a separate 
responsibility
+    
+    protected ReusableMachineTemplate registerTemplate(ReusableMachineTemplate 
template) {
+        registerTemplates(template);
+        return template;
+    }
+    protected void registerTemplates(ReusableMachineTemplate 
...templatesToReg) {
+        synchronized (templates) { 
+            for (ReusableMachineTemplate template: templatesToReg)
+                templates.add(template); 
+        }
+    }
+    
+    protected ReusableMachineTemplate newTemplate(String name) {
+        return registerTemplate(new ReusableMachineTemplate(name));
+    }
+
+    
+    public List<ReusableMachineTemplate> getTemplates() {
+        List<ReusableMachineTemplate> result;
+        synchronized (templates) { result = ImmutableList.copyOf(templates); }
+        return result;
+    }
+    
+    /** all machines matching any templates */
+    public MachineSet all() {
+        init();
+        return matchedMachines;
+    }
+
+    /** machines matching any templates which have not been claimed */
+    public MachineSet unclaimed() {
+        init();
+        synchronized (this) {
+            return matchedMachines.removed(claimedMachines);
+        }
+    }
+    
+    /** returns all machines matching the given criteria (may be claimed) */
+    @SuppressWarnings("unchecked")
+    public MachineSet all(Predicate<NodeMetadata> criterion) {
+        // To avoid generics complaints in callers caused by varargs, overload 
here
+        return all(new Predicate[] {criterion});
+    }
+    
+    /** returns all machines matching the given criteria (may be claimed) */
+    public MachineSet all(Predicate<NodeMetadata> ...ops) {
+        return new MachineSet(Iterables.filter(all(), compose(ops)));
+    }
+
+    /** returns unclaimed machines matching the given criteria */
+    @SuppressWarnings("unchecked")
+    public MachineSet unclaimed(Predicate<NodeMetadata> criterion) {
+        // To avoid generics complaints in callers caused by varargs, overload 
here
+        return unclaimed(new Predicate[] {criterion});
+    }
+    
+    /** returns unclaimed machines matching the given criteria */
+    public MachineSet unclaimed(Predicate<NodeMetadata> ...criteria) {
+        return new MachineSet(Iterables.filter(unclaimed(), 
compose(criteria)));
+    }
+
+    /** creates machines if necessary so that this spec exists (may already be 
claimed however) 
+     * returns a set of all matching machines, guaranteed non-empty 
+     * (but possibly some are already claimed) */
+    public MachineSet ensureExists(ReusableMachineTemplate template) {
+        return ensureExists(1, template);
+    }
+
+    public synchronized void addToBlacklist(MachineSet newToBlacklist) {
+        setBlacklist(blacklistedMachines.added(newToBlacklist));
+    }
+    
+    /** replaces the blacklist set; callers should generally perform a 
refresh()
+     * afterwards, to trigger re-detection of blacklisted machines
+     */
+    public synchronized void setBlacklist(MachineSet newBlacklist) {
+        blacklistedMachines = newBlacklist;
+        detectedMachines = detectedMachines.removed(blacklistedMachines);
+        matchedMachines = matchedMachines.removed(blacklistedMachines);
+    }
+    
+    /** creates machines if necessary so that this spec exists (may already be 
claimed however);
+     * returns a set of all matching machines, of size at least count (but 
possibly some are already claimed).
+     * (the pool can change at any point, so this set is a best-effort but may 
be out of date.
+     * see javadoc comments on this class.) */
+    public MachineSet ensureExists(int count, ReusableMachineTemplate 
template) {
+        MachineSet current;
+        current = all(matching(template));
+        if (current.size() >= count)
+            return current;
+        //have to create more
+        MachineSet moreNeeded = create(count-current.size(), template);
+        return current.added(moreNeeded);
+    }
+    
+    /** creates machines if necessary so that this spec can subsequently be 
claimed;
+     * returns all such unclaimed machines, guaranteed to be non-empty.
+    * (the pool can change at any point, so this set is a best-effort but may 
be out of date.
+    * see javadoc comments on this class.) */
+    public MachineSet ensureUnclaimed(ReusableMachineTemplate template) {
+        return ensureUnclaimed(1, template);
+    }
+
+    /** creates machines if necessary so that this spec can subsequently be 
claimed;
+     * returns a set of at least count unclaimed machines */
+    public MachineSet ensureUnclaimed(int count, ReusableMachineTemplate 
template) {
+        MachineSet current;
+        current = unclaimed(matching(template));
+        if (current.size() >= count)
+            return current;
+        //have to create more
+        MachineSet moreNeeded = create(count-current.size(), template);
+        return current.added(moreNeeded);
+    }
+
+    public Set<ReusableMachineTemplate> 
getTemplatesMatchingInstance(NodeMetadata nm) {
+        Set<ReusableMachineTemplate> result = new 
LinkedHashSet<ReusableMachineTemplate>(); 
+        for (ReusableMachineTemplate t: getTemplates()) {
+            if (matching(t).apply(nm)) {
+               result.add(t); 
+            }
+        }        
+        return result;
+    }
+    
+    /** creates the given number of machines of the indicated template */
+    public MachineSet create(int count, ReusableMachineTemplate template) {
+        Set<? extends NodeMetadata> nodes;
+        try {
+            Template t = template.newJcloudsTemplate(computeService);
+            if (log.isDebugEnabled()) log.debug("Creating "+count+" new 
instances of "+t);
+            nodes = computeService.createNodesInGroup(getPoolName(), count, t);
+        } catch (RunNodesException e) {
+            throw Throwables.propagate(e);
+        }
+        MachineSet result = new MachineSet(nodes);
+        registerNewNodes(result, template);
+        return result;
+    }
+    protected void registerNewNodes(MachineSet result, ReusableMachineTemplate 
template) {
+        for (NodeMetadata m: result) {
+            Set<ReusableMachineTemplate> ts = getTemplatesMatchingInstance(m);
+            if (ts.isEmpty()) {
+                log.error("Pool "+this+", created machine "+m+" from template 
"+template+", but no pool templates match!");
+            } else {
+                if (log.isDebugEnabled())
+                    log.debug("Pool "+this+", created machine "+m+" from 
template "+template+", matching templates "+ts);
+            }
+        }
+        synchronized (this) {
+            detectedMachines = detectedMachines.added(result);
+            matchedMachines = matchedMachines.added(result);
+        }
+    }
+
+    /** claims the indicated number of machines with the indicated spec, 
creating if necessary */
+    public MachineSet claim(int count, ReusableMachineTemplate t) {
+        init();
+        Set<NodeMetadata> claiming = new LinkedHashSet<NodeMetadata>();
+        while (claiming.size() < count) {
+            MachineSet mm = ensureUnclaimed(count - claiming.size(), t);
+            for (NodeMetadata m : mm) {
+                synchronized (this) {
+                    if (claiming.size() < count && 
!claimedMachines.contains(m)) {
+                        claiming.add(m);
+                        claimedMachines = claimedMachines.added(new 
MachineSet(m));
+                    }
+                }
+            }
+        }
+        MachineSet result = new MachineSet(claiming);
+        return result;
+    }
+
+
+    /** claims the indicated set of machines;
+     * throws exception if cannot all be claimed;
+     * returns the set passed in if successful */
+    public MachineSet claim(MachineSet set) {
+        init();
+        synchronized (this) {
+            MachineSet originalClaimed = claimedMachines;
+            claimedMachines = claimedMachines.added(set);
+            MachineSet newlyClaimed = claimedMachines.removed(originalClaimed);
+            if (newlyClaimed.size() != set.size()) {
+                //did not claim all; unclaim and fail
+                claimedMachines = originalClaimed;
+                MachineSet unavailable = set.removed(newlyClaimed); 
+                throw new IllegalArgumentException("Could not claim all 
requested machines; failed to claim "+unavailable);
+            }
+            return newlyClaimed;
+        }
+    }
+    
+    public int unclaim(MachineSet set) {
+        init();
+        synchronized (this) {
+            MachineSet originalClaimed = claimedMachines;
+            claimedMachines = claimedMachines.removed(set);
+            return originalClaimed.size() - claimedMachines.size();
+        }
+    }
+
+    
+    public int destroy(final MachineSet set) {
+        init();
+        synchronized (this) {
+            detectedMachines = detectedMachines.removed(set);
+            matchedMachines = matchedMachines.removed(set);
+            claimedMachines = claimedMachines.removed(set);
+        }
+        Set<? extends NodeMetadata> destroyed = 
computeService.destroyNodesMatching(new Predicate<NodeMetadata>() {
+            @Override
+            public boolean apply(NodeMetadata input) {
+                return set.contains(input);
+            }
+        });
+        synchronized (this) {
+            //in case a rescan happened while we were destroying
+            detectedMachines = detectedMachines.removed(set);
+            matchedMachines = matchedMachines.removed(set);
+            claimedMachines = claimedMachines.removed(set);
+        }
+        return destroyed.size();        
+    }
+        
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/pool/MachinePoolPredicates.java
----------------------------------------------------------------------
diff --git 
a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/pool/MachinePoolPredicates.java
 
b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/pool/MachinePoolPredicates.java
new file mode 100644
index 0000000..a988ab6
--- /dev/null
+++ 
b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/pool/MachinePoolPredicates.java
@@ -0,0 +1,149 @@
+/*
+ * 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.pool;
+
+import java.util.Map;
+
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.Processor;
+import org.jclouds.domain.Location;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.base.Throwables;
+
+public class MachinePoolPredicates {
+
+    private static final Logger log = 
LoggerFactory.getLogger(MachinePoolPredicates.class);
+    
+    public static Predicate<NodeMetadata> except(final MachineSet 
removedItems) {
+        return new Predicate<NodeMetadata>() {
+            @Override
+            public boolean apply(NodeMetadata input) {
+                return !removedItems.contains(input);
+            }
+        };
+    }
+
+    public static Predicate<NodeMetadata> except(final Predicate<NodeMetadata> 
predicateToExclude) {
+        return Predicates.not(predicateToExclude);
+    }
+
+    public static Predicate<NodeMetadata> matching(final 
ReusableMachineTemplate template) {
+        return new Predicate<NodeMetadata>() {
+            @Override
+            public boolean apply(NodeMetadata input) {
+                return matches(template, input);
+            }
+        };
+    }
+
+    public static Predicate<NodeMetadata> withTag(final String tag) {
+        return new Predicate<NodeMetadata>() {
+            @Override
+            public boolean apply(NodeMetadata input) {
+                return input.getTags().contains(tag);
+            }
+        };
+    }
+
+    public static Predicate<NodeMetadata> compose(final 
Predicate<NodeMetadata> ...predicates) {
+        return Predicates.and(predicates);
+    }
+
+    /** True iff the node matches the criteria specified in this template. 
+     * <p>
+     * NB: This only checks some of the most common fields, 
+     * plus a hashcode (in strict mode).  
+     * In strict mode you're practically guaranteed to match only machines 
created by this template.
+     * (Add a tag(uid) and you _will_ be guaranteed, strict mode or not.)
+     * <p> 
+     * Outside strict mode, some things (OS and hypervisor) can fall through 
the gaps.  
+     * But if that is a problem we can easily add them in.
+     * <p>
+     * (Caveat: If explicit Hardware, Image, and/or Template were specified in 
the template,
+     * then the hash code probably will not detect it.)   
+     **/
+    public static boolean matches(ReusableMachineTemplate template, 
NodeMetadata m) {
+        try {
+            // tags and user metadata
+
+            if (! m.getTags().containsAll( template.getTags(false) )) return 
false;
+
+            if (! isSubMapOf(template.getUserMetadata(false), 
m.getUserMetadata())) return false;
+
+
+            // common hardware parameters
+
+            if (template.getMinRam()!=null && m.getHardware().getRam() < 
template.getMinRam()) return false;
+
+            if (template.getMinCores()!=null) {
+                double numCores = 0;
+                for (Processor p: m.getHardware().getProcessors()) numCores += 
p.getCores();
+                if (numCores+0.001 < template.getMinCores()) return false;
+            }
+
+            if (template.getIs64bit()!=null) {
+                if (m.getOperatingSystem().is64Bit() != template.getIs64bit()) 
return false;
+            }
+
+            if (template.getOsFamily()!=null) {
+                if (m.getOperatingSystem() == null || 
+                        
!template.getOsFamily().equals(m.getOperatingSystem().getFamily())) return 
false;
+            }
+            if (template.getOsNameMatchesRegex()!=null) {
+                if (m.getOperatingSystem() == null || 
m.getOperatingSystem().getName()==null ||
+                        
!m.getOperatingSystem().getName().matches(template.getOsNameMatchesRegex())) 
return false;
+            }
+
+            if (template.getLocationId()!=null) {
+                if (!isLocationContainedIn(m.getLocation(), 
template.getLocationId())) return false;
+            }
+
+            // TODO other TemplateBuilder fields and TemplateOptions
+
+            return true;
+            
+        } catch (Exception e) {
+            log.warn("Error (rethrowing) trying to match "+m+" against 
"+template+": "+e, e);
+            throw Throwables.propagate(e);
+        }
+    }
+
+    private static boolean isLocationContainedIn(Location location, String 
locationId) {
+        if (location==null) return false;
+        if (locationId.equals(location.getId())) return true;
+        return isLocationContainedIn(location.getParent(), locationId);
+    }
+
+    public static boolean isSubMapOf(Map<String, String> sub, Map<String, 
String> bigger) {
+        for (Map.Entry<String, String> e: sub.entrySet()) {
+            if (e.getValue()==null) {
+                if (!bigger.containsKey(e.getKey())) return false;
+                if (bigger.get(e.getKey())!=null) return false;
+            } else {
+                if (!e.getValue().equals(bigger.get(e.getKey()))) return false;
+            }
+        }
+        return true;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/pool/MachineSet.java
----------------------------------------------------------------------
diff --git 
a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/pool/MachineSet.java
 
b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/pool/MachineSet.java
new file mode 100644
index 0000000..53116ab
--- /dev/null
+++ 
b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/pool/MachineSet.java
@@ -0,0 +1,98 @@
+/*
+ * 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.pool;
+
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import javax.annotation.concurrent.Immutable;
+
+import org.jclouds.compute.domain.NodeMetadata;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+
+@Immutable
+public class MachineSet implements Iterable<NodeMetadata> {
+
+    final Set<NodeMetadata> members;
+    
+    public MachineSet(Iterable<? extends NodeMetadata> m) { 
+        members = ImmutableSet.copyOf(m); 
+    }
+    public MachineSet(NodeMetadata ...nodes) {
+        members = ImmutableSet.copyOf(nodes); 
+    }
+
+    @Override
+    public Iterator<NodeMetadata> iterator() {
+        return members.iterator();
+    }
+
+    public MachineSet removed(MachineSet toRemove) {
+        Set<NodeMetadata> s = new LinkedHashSet<NodeMetadata>(members);
+        for (NodeMetadata m: toRemove) s.remove(m);
+        return new MachineSet(s);
+    }
+    public MachineSet added(MachineSet toAdd) {
+        Set<NodeMetadata> s = new LinkedHashSet<NodeMetadata>(members);
+        for (NodeMetadata m: toAdd) s.add(m);
+        return new MachineSet(s);
+    }
+
+    @SuppressWarnings("unchecked")
+    public MachineSet filtered(Predicate<NodeMetadata> criterion) {
+        // To avoid generics complaints in callers caused by varargs, overload 
here
+        return filtered(new Predicate[] {criterion});
+    }
+
+    public MachineSet filtered(Predicate<NodeMetadata> ...criteria) {
+        return new MachineSet(Iterables.filter(members, 
MachinePoolPredicates.compose(criteria)));
+    }
+
+    public int size() {
+        return members.size();
+    }
+    
+    public boolean isEmpty() {
+        return members.isEmpty();
+    }
+    
+    public boolean contains(NodeMetadata input) {
+        return members.contains(input);
+    }
+    
+    @Override
+    public String toString() {
+        return members.toString();
+    }
+    
+    @Override
+    public int hashCode() {
+        return members.hashCode();
+    }
+    
+    @Override
+    public boolean equals(Object obj) {
+        return (obj instanceof MachineSet) && (members.equals( 
((MachineSet)obj).members ));
+    }
+    
+}

Reply via email to