You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by ha...@apache.org on 2015/08/14 05:43:05 UTC

[36/54] incubator-brooklyn git commit: [BROOKLYN-162] Renaming package brooklyn.location

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/ResourceUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/ResourceUtils.java b/core/src/main/java/brooklyn/util/ResourceUtils.java
index a39f363..cb350af 100644
--- a/core/src/main/java/brooklyn/util/ResourceUtils.java
+++ b/core/src/main/java/brooklyn/util/ResourceUtils.java
@@ -53,7 +53,7 @@ import org.slf4j.LoggerFactory;
 import brooklyn.catalog.internal.BasicBrooklynCatalog.BrooklynLoaderTracker;
 import brooklyn.catalog.internal.CatalogUtils;
 import brooklyn.internal.BrooklynInitialization;
-import brooklyn.location.basic.SshMachineLocation;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
 import brooklyn.management.classloading.JavaBrooklynClassLoadingContext;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.exceptions.Exceptions;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/file/ArchiveTasks.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/file/ArchiveTasks.java b/core/src/main/java/brooklyn/util/file/ArchiveTasks.java
index 9c6ed2f..b183f62 100644
--- a/core/src/main/java/brooklyn/util/file/ArchiveTasks.java
+++ b/core/src/main/java/brooklyn/util/file/ArchiveTasks.java
@@ -23,7 +23,7 @@ import java.util.Map;
 import org.apache.brooklyn.api.management.TaskAdaptable;
 import org.apache.brooklyn.api.management.TaskFactory;
 
-import brooklyn.location.basic.SshMachineLocation;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
 import brooklyn.util.ResourceUtils;
 import brooklyn.util.net.Urls;
 import brooklyn.util.task.Tasks;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/file/ArchiveUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/file/ArchiveUtils.java b/core/src/main/java/brooklyn/util/file/ArchiveUtils.java
index e8233c2..d072b70 100644
--- a/core/src/main/java/brooklyn/util/file/ArchiveUtils.java
+++ b/core/src/main/java/brooklyn/util/file/ArchiveUtils.java
@@ -31,7 +31,7 @@ import java.util.Set;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import brooklyn.location.basic.SshMachineLocation;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
 import brooklyn.util.ResourceUtils;
 import brooklyn.util.collections.MutableList;
 import brooklyn.util.collections.MutableMap;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/task/ssh/SshFetchTaskFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/task/ssh/SshFetchTaskFactory.java b/core/src/main/java/brooklyn/util/task/ssh/SshFetchTaskFactory.java
index 31f654c..bd9e96e 100644
--- a/core/src/main/java/brooklyn/util/task/ssh/SshFetchTaskFactory.java
+++ b/core/src/main/java/brooklyn/util/task/ssh/SshFetchTaskFactory.java
@@ -22,7 +22,7 @@ import org.apache.brooklyn.api.management.TaskFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import brooklyn.location.basic.SshMachineLocation;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
 import brooklyn.util.config.ConfigBag;
 
 // cannot be (cleanly) instantiated due to nested generic self-referential type; however trivial subclasses do allow it 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/task/ssh/SshFetchTaskWrapper.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/task/ssh/SshFetchTaskWrapper.java b/core/src/main/java/brooklyn/util/task/ssh/SshFetchTaskWrapper.java
index 1d265dc..9553b4f 100644
--- a/core/src/main/java/brooklyn/util/task/ssh/SshFetchTaskWrapper.java
+++ b/core/src/main/java/brooklyn/util/task/ssh/SshFetchTaskWrapper.java
@@ -27,7 +27,7 @@ import org.apache.brooklyn.api.management.TaskWrapper;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.FilenameUtils;
 
-import brooklyn.location.basic.SshMachineLocation;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
 import brooklyn.util.config.ConfigBag;
 import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.os.Os;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/task/ssh/SshPutTaskFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/task/ssh/SshPutTaskFactory.java b/core/src/main/java/brooklyn/util/task/ssh/SshPutTaskFactory.java
index 381341a..e2c5502 100644
--- a/core/src/main/java/brooklyn/util/task/ssh/SshPutTaskFactory.java
+++ b/core/src/main/java/brooklyn/util/task/ssh/SshPutTaskFactory.java
@@ -25,7 +25,7 @@ import org.apache.brooklyn.api.management.TaskFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import brooklyn.location.basic.SshMachineLocation;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
 import brooklyn.util.stream.KnownSizeInputStream;
 import brooklyn.util.stream.ReaderInputStream;
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/task/ssh/SshPutTaskStub.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/task/ssh/SshPutTaskStub.java b/core/src/main/java/brooklyn/util/task/ssh/SshPutTaskStub.java
index ec99dc8..185e819 100644
--- a/core/src/main/java/brooklyn/util/task/ssh/SshPutTaskStub.java
+++ b/core/src/main/java/brooklyn/util/task/ssh/SshPutTaskStub.java
@@ -20,7 +20,7 @@ package brooklyn.util.task.ssh;
 
 import java.io.InputStream;
 
-import brooklyn.location.basic.SshMachineLocation;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
 import brooklyn.util.config.ConfigBag;
 
 import com.google.common.base.Supplier;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/task/ssh/SshTasks.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/task/ssh/SshTasks.java b/core/src/main/java/brooklyn/util/task/ssh/SshTasks.java
index bf01bfe..b13b43c 100644
--- a/core/src/main/java/brooklyn/util/task/ssh/SshTasks.java
+++ b/core/src/main/java/brooklyn/util/task/ssh/SshTasks.java
@@ -35,10 +35,10 @@ import brooklyn.config.ConfigKey;
 import brooklyn.config.ConfigUtils;
 import brooklyn.entity.basic.BrooklynTaskTags;
 import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.location.Location;
-import brooklyn.location.basic.AbstractLocation;
-import brooklyn.location.basic.LocationInternal;
-import brooklyn.location.basic.SshMachineLocation;
+import org.apache.brooklyn.location.Location;
+import org.apache.brooklyn.location.basic.AbstractLocation;
+import org.apache.brooklyn.location.basic.LocationInternal;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
 import brooklyn.util.ResourceUtils;
 import brooklyn.util.config.ConfigBag;
 import brooklyn.util.internal.ssh.SshTool;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/task/ssh/internal/AbstractSshExecTaskFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/task/ssh/internal/AbstractSshExecTaskFactory.java b/core/src/main/java/brooklyn/util/task/ssh/internal/AbstractSshExecTaskFactory.java
index c78ce5d..86764f3 100644
--- a/core/src/main/java/brooklyn/util/task/ssh/internal/AbstractSshExecTaskFactory.java
+++ b/core/src/main/java/brooklyn/util/task/ssh/internal/AbstractSshExecTaskFactory.java
@@ -20,7 +20,7 @@ package brooklyn.util.task.ssh.internal;
 
 import com.google.common.base.Preconditions;
 
-import brooklyn.location.basic.SshMachineLocation;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
 import brooklyn.util.config.ConfigBag;
 import brooklyn.util.task.system.ProcessTaskFactory;
 import brooklyn.util.task.system.ProcessTaskWrapper;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/task/ssh/internal/PlainSshExecTaskFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/task/ssh/internal/PlainSshExecTaskFactory.java b/core/src/main/java/brooklyn/util/task/ssh/internal/PlainSshExecTaskFactory.java
index a7c6994..efc14db 100644
--- a/core/src/main/java/brooklyn/util/task/ssh/internal/PlainSshExecTaskFactory.java
+++ b/core/src/main/java/brooklyn/util/task/ssh/internal/PlainSshExecTaskFactory.java
@@ -20,8 +20,7 @@ package brooklyn.util.task.ssh.internal;
 
 import java.util.List;
 
-import brooklyn.location.basic.SshMachineLocation;
-import brooklyn.util.config.ConfigBag;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
 import brooklyn.util.task.system.ProcessTaskWrapper;
 
 import com.google.common.base.Function;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/task/system/ProcessTaskFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/task/system/ProcessTaskFactory.java b/core/src/main/java/brooklyn/util/task/system/ProcessTaskFactory.java
index f43e47a..407111c 100644
--- a/core/src/main/java/brooklyn/util/task/system/ProcessTaskFactory.java
+++ b/core/src/main/java/brooklyn/util/task/system/ProcessTaskFactory.java
@@ -23,7 +23,7 @@ import java.util.Map;
 import org.apache.brooklyn.api.management.TaskFactory;
 
 import brooklyn.config.ConfigKey;
-import brooklyn.location.basic.SshMachineLocation;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
 import brooklyn.util.internal.ssh.SshTool;
 import brooklyn.util.task.system.ProcessTaskStub.ScriptReturnType;
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/task/system/ProcessTaskStub.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/task/system/ProcessTaskStub.java b/core/src/main/java/brooklyn/util/task/system/ProcessTaskStub.java
index 9fd19e7..df37691 100644
--- a/core/src/main/java/brooklyn/util/task/system/ProcessTaskStub.java
+++ b/core/src/main/java/brooklyn/util/task/system/ProcessTaskStub.java
@@ -22,7 +22,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
-import brooklyn.location.basic.SshMachineLocation;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.config.ConfigBag;
 import brooklyn.util.text.Strings;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/task/system/internal/AbstractProcessTaskFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/task/system/internal/AbstractProcessTaskFactory.java b/core/src/main/java/brooklyn/util/task/system/internal/AbstractProcessTaskFactory.java
index 5aa84c0..e41a9a9 100644
--- a/core/src/main/java/brooklyn/util/task/system/internal/AbstractProcessTaskFactory.java
+++ b/core/src/main/java/brooklyn/util/task/system/internal/AbstractProcessTaskFactory.java
@@ -26,7 +26,7 @@ import org.slf4j.LoggerFactory;
 
 import brooklyn.config.ConfigKey;
 import brooklyn.entity.basic.BrooklynTaskTags;
-import brooklyn.location.basic.SshMachineLocation;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
 import brooklyn.util.stream.Streams;
 import brooklyn.util.task.TaskBuilder;
 import brooklyn.util.task.system.ProcessTaskFactory;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/task/system/internal/ExecWithLoggingHelpers.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/task/system/internal/ExecWithLoggingHelpers.java b/core/src/main/java/brooklyn/util/task/system/internal/ExecWithLoggingHelpers.java
index f39d7b6..c2b8907 100644
--- a/core/src/main/java/brooklyn/util/task/system/internal/ExecWithLoggingHelpers.java
+++ b/core/src/main/java/brooklyn/util/task/system/internal/ExecWithLoggingHelpers.java
@@ -28,7 +28,7 @@ import java.util.Map;
 import org.slf4j.Logger;
 
 import brooklyn.config.ConfigKey;
-import brooklyn.location.basic.SshMachineLocation;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.config.ConfigBag;
 import brooklyn.util.flags.TypeCoercions;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/task/system/internal/SystemProcessTaskFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/task/system/internal/SystemProcessTaskFactory.java b/core/src/main/java/brooklyn/util/task/system/internal/SystemProcessTaskFactory.java
index 9b40c7f..e6eb831 100644
--- a/core/src/main/java/brooklyn/util/task/system/internal/SystemProcessTaskFactory.java
+++ b/core/src/main/java/brooklyn/util/task/system/internal/SystemProcessTaskFactory.java
@@ -23,7 +23,7 @@ import java.io.File;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import brooklyn.location.basic.SshMachineLocation;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.config.ConfigBag;
 import brooklyn.util.internal.ssh.ShellTool;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/brooklyn/util/text/TemplateProcessor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/text/TemplateProcessor.java b/core/src/main/java/brooklyn/util/text/TemplateProcessor.java
index 8fdaebb..f936e93 100644
--- a/core/src/main/java/brooklyn/util/text/TemplateProcessor.java
+++ b/core/src/main/java/brooklyn/util/text/TemplateProcessor.java
@@ -37,7 +37,7 @@ import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityInternal;
 import brooklyn.event.basic.DependentConfiguration;
 import brooklyn.event.basic.Sensors;
-import brooklyn.location.Location;
+import org.apache.brooklyn.location.Location;
 import brooklyn.management.internal.ManagementContextInternal;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.exceptions.Exceptions;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/access/BrooklynAccessUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/access/BrooklynAccessUtils.java b/core/src/main/java/org/apache/brooklyn/location/access/BrooklynAccessUtils.java
new file mode 100644
index 0000000..eebee14
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/location/access/BrooklynAccessUtils.java
@@ -0,0 +1,143 @@
+/*
+ * 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.access;
+
+import java.util.Collection;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.location.Location;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.event.basic.BasicConfigKey;
+import org.apache.brooklyn.location.MachineLocation;
+import org.apache.brooklyn.location.basic.Machines;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+import org.apache.brooklyn.location.basic.SupportsPortForwarding;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.guava.Maybe;
+import brooklyn.util.net.Cidr;
+import brooklyn.util.task.DynamicTasks;
+import brooklyn.util.task.Tasks;
+import brooklyn.util.task.ssh.SshTasks;
+import brooklyn.util.task.system.ProcessTaskWrapper;
+import brooklyn.util.text.Strings;
+
+import com.google.common.base.Supplier;
+import com.google.common.net.HostAndPort;
+
+public class BrooklynAccessUtils {
+
+    private static final Logger log = LoggerFactory.getLogger(BrooklynAccessUtils.class);
+    
+    public static final ConfigKey<PortForwardManager> PORT_FORWARDING_MANAGER = new BasicConfigKey<PortForwardManager>(
+            PortForwardManager.class, "brooklyn.portforwarding.manager", "A port-forwarding manager to use at an entity "
+                + "or a location, where supported; note this should normally be a serializable client instance to prevent "
+                + "the creation of multiple disconnected instances via config duplication");
+    
+    public static final ConfigKey<Cidr> MANAGEMENT_ACCESS_CIDR = new BasicConfigKey<Cidr>(
+            Cidr.class, "brooklyn.portforwarding.management.cidr", "CIDR to enable by default for port-forwarding for management",
+            null);  // TODO should be a list
+
+    public static HostAndPort getBrooklynAccessibleAddress(Entity entity, int port) {
+        String host;
+        
+        // look up port forwarding
+        PortForwardManager pfw = entity.getConfig(PORT_FORWARDING_MANAGER);
+        if (pfw!=null) {
+            Collection<Location> ll = entity.getLocations();
+            Maybe<SupportsPortForwarding> machine = Machines.findUniqueElement(ll, SupportsPortForwarding.class);
+            if (machine.isPresent()) {
+                synchronized (BrooklynAccessUtils.class) {
+                    // TODO finer-grained synchronization
+                    
+                    HostAndPort hp = pfw.lookup((MachineLocation)machine.get(), port);
+                    if (hp!=null) return hp;
+                    
+                    Location l = (Location) machine.get();
+                    if (l instanceof SupportsPortForwarding) {
+                        Cidr source = entity.getConfig(MANAGEMENT_ACCESS_CIDR);
+                        if (source!=null) {
+                            log.debug("BrooklynAccessUtils requesting new port-forwarding rule to access "+port+" on "+entity+" (at "+l+", enabled for "+source+")");
+                            // TODO discuss, is this the best way to do it
+                            // (will probably _create_ the port forwarding rule!)
+                            hp = ((SupportsPortForwarding) l).getSocketEndpointFor(source, port);
+                            if (hp!=null) return hp;
+                        } else {
+                            log.warn("No "+MANAGEMENT_ACCESS_CIDR.getName()+" configured for "+entity+", so cannot forward port "+port+" " +
+                                    "even though "+PORT_FORWARDING_MANAGER.getName()+" was supplied");
+                        }
+                    }
+                }
+            }
+        }
+        
+        host = entity.getAttribute(Attributes.HOSTNAME);
+        if (host!=null) return HostAndPort.fromParts(host, port);
+        
+        throw new IllegalStateException("Cannot find way to access port "+port+" on "+entity+" from Brooklyn (no host.name)");
+    }
+
+    /** attempts to resolve hostnameTarget from origin
+     * @return null if it definitively can't be resolved,  
+     * best-effort IP address if possible, or blank if we could not run ssh or make sense of the output */
+    public static String getResolvedAddress(Entity entity, SshMachineLocation origin, String hostnameTarget) {
+        ProcessTaskWrapper<Integer> task = SshTasks.newSshExecTaskFactory(origin, "ping -c 1 -t 1 "+hostnameTarget)
+            .summary("checking resolution of "+hostnameTarget).allowingNonZeroExitCode().newTask();
+        DynamicTasks.queueIfPossible(task).orSubmitAndBlock(entity).asTask().blockUntilEnded();
+        if (task.asTask().isError()) {
+            log.warn("ping could not be run, at "+entity+" / "+origin+": "+Tasks.getError(task.asTask()));
+            return "";
+        }
+        if (task.getExitCode()==null || task.getExitCode()!=0) {
+            if (task.getExitCode()!=null && task.getExitCode()<10) {
+                // small number means ping failed to resolve or ping the hostname
+                log.debug("not able to resolve "+hostnameTarget+" from "+origin+" for "+entity+" because exit code was "+task.getExitCode());
+                return null;
+            }
+            // large number means ping probably did not run
+            log.warn("ping not run as expected, at "+entity+" / "+origin+" (code "+task.getExitCode()+"):\n"+task.getStdout().trim()+" --- "+task.getStderr().trim());
+            return "";
+        }
+        String out = task.getStdout();
+        try {
+            String line1 = Strings.getFirstLine(out);
+            String ip = Strings.getFragmentBetween(line1, "(", ")");
+            if (Strings.isNonBlank(ip)) 
+                return ip;
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            /* ignore non-parseable output */ 
+        }
+        if (out.contains("127.0.0.1")) return "127.0.0.1";
+        return "";
+    }
+
+    public static Supplier<String> resolvedAddressSupplier(final Entity entity, final SshMachineLocation origin, final String hostnameTarget) {
+        return new Supplier<String>() {
+            @Override
+            public String get() {
+                return getResolvedAddress(entity, origin, hostnameTarget);
+            }
+        };
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManager.java b/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManager.java
new file mode 100644
index 0000000..922231b
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManager.java
@@ -0,0 +1,327 @@
+/*
+ * 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.access;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.ConfigKeys;
+import org.apache.brooklyn.location.Location;
+import com.google.common.annotations.Beta;
+import com.google.common.base.Objects;
+import com.google.common.base.Predicate;
+import com.google.common.net.HostAndPort;
+
+import java.util.Collection;
+
+/**
+ * Acts as a registry for existing port mappings (e.g. the public endpoints for accessing specific
+ * ports on private VMs). This could be using DNAT, or iptables port-forwarding, or Docker port-mapping 
+ * via the host, or any other port mapping approach.
+ * 
+ * Also controls the allocation of ports via {@link #acquirePublicPort(String)}
+ * (e.g. for port-mapping with DNAT, then which port to use for the public side).
+ * 
+ * Implementations typically will not know anything about what the firewall/IP actually is, they just 
+ * handle a unique identifier for it.
+ * 
+ * To use, see {@link PortForwardManagerLocationResolver}, with code such as 
+ * {@code managementContext.getLocationRegistry().resolve("portForwardManager(scope=global)")}.
+ * 
+ * @see PortForwardManagerImpl for implementation notes and considerations.
+ */
+@Beta
+public interface PortForwardManager extends Location {
+
+    @Beta
+    class AssociationMetadata {
+        private final String publicIpId;
+        private final HostAndPort publicEndpoint;
+        private final Location location;
+        private final int privatePort;
+
+        /**
+         * Users are discouraged from calling this constructor; the signature may change in future releases.
+         * Instead, instances will be created automatically by Brooklyn to be passed to the
+         * {@link AssociationListener#onAssociationCreated(AssociationMetadata)} method.
+         */
+        public AssociationMetadata(String publicIpId, HostAndPort publicEndpoint, Location location, int privatePort) {
+            this.publicIpId = publicIpId;
+            this.publicEndpoint = publicEndpoint;
+            this.location = location;
+            this.privatePort = privatePort;
+        }
+
+        public String getPublicIpId() {
+            return publicIpId;
+        }
+
+        public HostAndPort getPublicEndpoint() {
+            return publicEndpoint;
+        }
+
+        public Location getLocation() {
+            return location;
+        }
+
+        public int getPrivatePort() {
+            return privatePort;
+        }
+
+        public String toString() {
+            return Objects.toStringHelper(this)
+                    .add("publicIpId", publicIpId)
+                    .add("publicEndpoint", publicEndpoint)
+                    .add("location", location)
+                    .add("privatePort", privatePort)
+                    .toString();
+        }
+    }
+
+    @Beta
+    interface AssociationListener {
+        void onAssociationCreated(AssociationMetadata metadata);
+        void onAssociationDeleted(AssociationMetadata metadata);
+    }
+
+    /**
+     * The intention is that there is one PortForwardManager instance per "scope". If you 
+     * use global, then it will be a shared instance (for that management context). If you 
+     * pass in your own name (e.g. "docker-fjie3") then it will shared with just any other
+     * places that use that same location spec (e.g. {@code portForwardManager(scope=docker-fjie3)}).
+     */
+    // TODO Note: using name "scope" rather than "brooklyn.portForwardManager.scope" so that location spec 
+    // "portForwardManager(scope=global)" works, rather than having to do 
+    // portForwardManager(brooklyn.portForwardManager.scope=global).
+    // The config being read by the PortForwardManagerLocationResolver doesn't respect @SetFromFlag("scope").
+    public static final ConfigKey<String> SCOPE = ConfigKeys.newStringConfigKey(
+            "scope",
+            "The scope that this applies to, defaulting to global",
+            "global");
+
+    @Beta
+    public static final ConfigKey<Integer> PORT_FORWARD_MANAGER_STARTING_PORT = ConfigKeys.newIntegerConfigKey(
+            "brooklyn.portForwardManager.startingPort",
+            "The starting port for assigning port numbers, such as for DNAT",
+            11000);
+
+    public String getScope();
+
+    /**
+     * Reserves a unique public port on the given publicIpId.
+     * <p>
+     * Often followed by {@link #associate(String, HostAndPort, int)} or {@link #associate(String, HostAndPort, Location, int)}
+     * to enable {@link #lookup(String, int)} or {@link #lookup(Location, int)} respectively.
+     */
+    public int acquirePublicPort(String publicIpId);
+
+    /**
+     * Records a location and private port against a public endpoint (ip and port),
+     * to support {@link #lookup(Location, int)}.
+     * <p>
+     * Superfluous if {@link #acquirePublicPort(String, Location, int)} was used,
+     * but strongly recommended if {@link #acquirePublicPortExplicit(String, int)} was used
+     * e.g. if the location is not known ahead of time.
+     */
+    public void associate(String publicIpId, HostAndPort publicEndpoint, Location l, int privatePort);
+
+    /**
+     * Records a mapping for publicIpId:privatePort to a public endpoint, such that it can
+     * subsequently be looked up using {@link #lookup(String, int)}.
+     */
+    public void associate(String publicIpId, HostAndPort publicEndpoint, int privatePort);
+
+    /**
+     * Registers a listener, which will be notified each time a new port mapping is associated. See {@link #associate(String, HostAndPort, int)}
+     * and {@link #associate(String, HostAndPort, Location, int)}.
+     */
+    @Beta
+    public void addAssociationListener(AssociationListener listener, Predicate<? super AssociationMetadata> filter);
+
+    @Beta
+    public void removeAssociationListener(AssociationListener listener);
+    
+    /**
+     * Returns the public ip hostname and public port for use contacting the given endpoint.
+     * <p>
+     * Will return null if:
+     * <ul>
+     * <li>No publicPort is associated with this location and private port.
+     * <li>No publicIpId is associated with this location and private port.
+     * <li>No publicIpHostname is recorded against the associated publicIpId.
+     * </ul>
+     * Conceivably this may have to be access-location specific.
+     *
+     * @see #recordPublicIpHostname(String, String)
+     */
+    public HostAndPort lookup(Location l, int privatePort);
+
+    /**
+     * Returns the public endpoint (host and port) for use contacting the given endpoint.
+     * 
+     * Expects a previous call to {@link #associate(String, HostAndPort, int)}, to register
+     * the endpoint.
+     * 
+     * Will return null if there has not been a public endpoint associated with this pairing.
+     */
+    public HostAndPort lookup(String publicIpId, int privatePort);
+
+    /** 
+     * Clears the given port mapping, returning true if there was a match.
+     */
+    public boolean forgetPortMapping(String publicIpId, int publicPort);
+    
+    /** 
+     * Clears the port mappings associated with the given location, returning true if there were any matches.
+     */
+    public boolean forgetPortMappings(Location location);
+    
+    /** 
+     * Clears the port mappings associated with the given publicIpId, returning true if there were any matches.
+     */
+    public boolean forgetPortMappings(String publicIpId);
+    
+    public String toVerboseString();
+
+    
+    ///////////////////////////////////////////////////////////////////////////////////
+    // Deprecated
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Reserves a unique public port for the purpose of forwarding to the given target,
+     * associated with a given location for subsequent lookup purpose.
+     * <p>
+     * If already allocated, returns the previously allocated.
+     * 
+     * @deprecated since 0.7.0; use {@link #acquirePublicPort(String)}, and then use {@link #associate(String, HostAndPort, int)} or {@link #associate(String, HostAndPort, Location, int)}
+     */
+    @Deprecated
+    public int acquirePublicPort(String publicIpId, Location l, int privatePort);
+
+    /** 
+     * Returns old mapping if it existed, null if it is new.
+     * 
+     * @deprecated since 0.7.0; use {@link #associate(String, HostAndPort, int)} or {@link #associate(String, HostAndPort, Location, int)}
+     */
+    @Deprecated
+    public PortMapping acquirePublicPortExplicit(String publicIpId, int port);
+
+    /**
+     * Records a location and private port against a publicIp and public port,
+     * to support {@link #lookup(Location, int)}.
+     * <p>
+     * Superfluous if {@link #acquirePublicPort(String, Location, int)} was used,
+     * but strongly recommended if {@link #acquirePublicPortExplicit(String, int)} was used
+     * e.g. if the location is not known ahead of time.
+     * 
+     * @deprecated Use {@link #associate(String, HostAndPort, Location, int)}
+     */
+    @Deprecated
+    public void associate(String publicIpId, int publicPort, Location l, int privatePort);
+
+    /**
+     * Records a public hostname or address to be associated with the given publicIpId for lookup purposes.
+     * <p>
+     * Conceivably this may have to be access-location specific.
+     * 
+     * @deprecated Use {@link #associate(String, HostAndPort, int)} or {@link #associate(String, HostAndPort, Location, int)}
+     */
+    @Deprecated
+    public void recordPublicIpHostname(String publicIpId, String hostnameOrPublicIpAddress);
+
+    /**
+     * Returns a recorded public hostname or address.
+     * 
+     * @deprecated Use {@link #lookup(String, int)} or {@link #lookup(Location, int)}
+     */
+    @Deprecated
+    public String getPublicIpHostname(String publicIpId);
+    
+    /**
+     * Clears a previous call to {@link #recordPublicIpHostname(String, String)}.
+     * 
+     * @deprecated Use {@link #forgetPortMapping(String, int)} or {@link #forgetPortMappings(Location)}
+     */
+    @Deprecated
+    public boolean forgetPublicIpHostname(String publicIpId);
+
+    /**
+     * Returns true if this implementation is a client which is immutable/safe for serialization
+     * i.e. it delegates to something on an entity or location elsewhere.
+     * 
+     * @deprecated since 0.7.0; no need to separate client-proxy from impl
+     */
+    @Deprecated
+    public boolean isClient();
+    
+
+    ///////////////////////////////////////////////////////////////////////////////////
+    // Deprecated; just internal
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    /** 
+     * Returns the port mapping for a given publicIpId and public port.
+     * 
+     * @deprecated since 0.7.0; this method will be internal only
+     */
+    @Deprecated
+    public PortMapping getPortMappingWithPublicSide(String publicIpId, int publicPort);
+
+    /** 
+     * Returns the subset of port mappings associated with a given public IP ID.
+     * 
+     * @deprecated since 0.7.0; this method will be internal only
+     */
+    @Deprecated
+    public Collection<PortMapping> getPortMappingWithPublicIpId(String publicIpId);
+
+    /** 
+     * @see {@link #forgetPortMapping(String, int)} and {@link #forgetPortMappings(Location)}
+     * 
+     * @deprecated since 0.7.0; this method will be internal only
+     */
+    @Deprecated
+    public boolean forgetPortMapping(PortMapping m);
+
+    /**
+     * Returns the public host and port for use accessing the given mapping.
+     * <p>
+     * Conceivably this may have to be access-location specific.
+     * 
+     * @deprecated since 0.7.0; this method will be internal only
+     */
+    @Deprecated
+    public HostAndPort getPublicHostAndPort(PortMapping m);
+
+    /** 
+     * Returns the subset of port mappings associated with a given location.
+     * 
+     * @deprecated since 0.7.0; this method will be internal only
+     */
+    @Deprecated
+    public Collection<PortMapping> getLocationPublicIpIds(Location l);
+        
+    /** 
+     * Returns the mapping to a given private port, or null if none.
+     * 
+     * @deprecated since 0.7.0; this method will be internal only
+     */
+    @Deprecated
+    public PortMapping getPortMappingWithPrivateSide(Location l, int privatePort);
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerAuthority.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerAuthority.java b/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerAuthority.java
new file mode 100644
index 0000000..da7d098
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerAuthority.java
@@ -0,0 +1,47 @@
+/*
+ * 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.access;
+
+
+import org.apache.brooklyn.api.entity.Entity;
+
+import brooklyn.entity.basic.EntityInternal;
+
+/**
+ * @deprecated since 0.7.0; use {@link PortForwardManagerImpl}
+ */
+@Deprecated
+public class PortForwardManagerAuthority extends PortForwardManagerImpl {
+    private Entity owningEntity;
+
+    public PortForwardManagerAuthority() {
+    }
+
+    public PortForwardManagerAuthority(Entity owningEntity) {
+        this.owningEntity = owningEntity;
+    }
+
+    protected void onChanged() {
+        if (owningEntity != null) {
+            ((EntityInternal) owningEntity).requestPersist();
+        } else {
+            super.onChanged();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerClient.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerClient.java b/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerClient.java
new file mode 100644
index 0000000..09aea72
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerClient.java
@@ -0,0 +1,406 @@
+/*
+ * 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.access;
+
+import java.util.Collection;
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.event.AttributeSensor;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.config.ConfigKey.HasConfigKey;
+import org.apache.brooklyn.location.Location;
+import brooklyn.util.exceptions.Exceptions;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
+import com.google.common.net.HostAndPort;
+
+/**
+ * @deprecated since 0.7.0; just use the {@link PortForwardManager}, or a direct reference to its impl {@link PortForwardManagerImpl}
+ */
+@Deprecated
+public class PortForwardManagerClient implements PortForwardManager {
+
+    private static final long serialVersionUID = -295204304305332895L;
+    
+    protected final Supplier<PortForwardManager> delegateSupplier;
+    private transient volatile PortForwardManager _delegate;
+    
+    protected PortForwardManagerClient(Supplier<PortForwardManager> supplier) {
+        this.delegateSupplier = supplier;
+    }
+    
+    /** creates an instance given a supplier; 
+     * the supplier should be brooklyn-persistable, that is to say
+     * references should be in terms of entities/locations 
+     * which can retrieve an authoritative source even under cloning */
+    public static PortForwardManager fromSupplier(Supplier<PortForwardManager> supplier) {
+        return new PortForwardManagerClient(supplier);
+    }
+
+    /** creates an instance given an entity and an interface method it implements to retrieve the PortForwardManager */ 
+    public static PortForwardManager fromMethodOnEntity(final Entity entity, final String getterMethodOnEntity) {
+        Preconditions.checkNotNull(entity);
+        Preconditions.checkNotNull(getterMethodOnEntity);
+        return new PortForwardManagerClient(new Supplier<PortForwardManager>() {
+            @Override
+            public PortForwardManager get() {
+                PortForwardManager result;
+                try {
+                    result = (PortForwardManager) entity.getClass().getMethod(getterMethodOnEntity).invoke(entity);
+                } catch (Exception e) {
+                    Exceptions.propagateIfFatal(e);
+                    throw new IllegalStateException("Cannot invoke "+getterMethodOnEntity+" on "+entity+" ("+entity.getClass()+"): "+e, e);
+                }
+                if (result==null)
+                    throw new IllegalStateException("No PortForwardManager available via "+getterMethodOnEntity+" on "+entity+" (returned null)");
+                return result;
+            }
+        });
+    }
+
+    /** creates an instance given an entity and {@link AttributeSensor} to retrieve the PortForwardManager */ 
+    public static PortForwardManager fromAttributeOnEntity(final Entity entity, final AttributeSensor<PortForwardManager> attributeOnEntity) {
+        Preconditions.checkNotNull(entity);
+        Preconditions.checkNotNull(attributeOnEntity);
+        return new PortForwardManagerClient(new Supplier<PortForwardManager>() {
+            @Override
+            public PortForwardManager get() {
+                PortForwardManager result = entity.getAttribute(attributeOnEntity);
+                if (result==null)
+                    throw new IllegalStateException("No PortForwardManager available via "+attributeOnEntity+" on "+entity+" (returned null)");
+                return result;
+            }
+        });
+    }
+    
+    protected PortForwardManager getDelegate() {
+        if (_delegate==null) {
+            _delegate = delegateSupplier.get();
+        }
+        return _delegate;
+    }
+
+    @Override
+    public int acquirePublicPort(String publicIpId) {
+        return getDelegate().acquirePublicPort(publicIpId);
+    }
+
+    @Override
+    public void associate(String publicIpId, HostAndPort publicEndpoint, Location l, int privatePort) {
+        getDelegate().associate(publicIpId, publicEndpoint, l, privatePort);
+    }
+
+    @Override
+    public void associate(String publicIpId, HostAndPort publicEndpoint, int privatePort) {
+        getDelegate().associate(publicIpId, publicEndpoint, privatePort);
+    }
+
+    @Override
+    public HostAndPort lookup(Location l, int privatePort) {
+        return getDelegate().lookup(l, privatePort);
+    }
+
+    @Override
+    public HostAndPort lookup(String publicIpId, int privatePort) {
+        return getDelegate().lookup(publicIpId, privatePort);
+    }
+
+    @Override
+    public boolean forgetPortMapping(String publicIpId, int publicPort) {
+        return getDelegate().forgetPortMapping(publicIpId, publicPort);
+    }
+
+    @Override
+    public boolean forgetPortMappings(Location location) {
+        return getDelegate().forgetPortMappings(location);
+    }
+
+    @Override
+    public boolean forgetPortMappings(String publicIpId) {
+        return getDelegate().forgetPortMappings(publicIpId);
+    }
+
+    @Override
+    public String getId() {
+        return getDelegate().getId();
+    }
+
+    @Override
+    public String getScope() {
+        return getDelegate().getScope();
+    }
+
+    @Override
+    public void addAssociationListener(AssociationListener listener, Predicate<? super AssociationMetadata> filter) {
+        getDelegate().addAssociationListener(listener, filter);
+    }
+
+    @Override
+    public void removeAssociationListener(AssociationListener listener) {
+        getDelegate().removeAssociationListener(listener);
+    }
+
+    @Override
+    public String toVerboseString() {
+        return getClass().getName()+"[wrapping="+getDelegate().toVerboseString()+"]";
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////
+    // Deprecated
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Reserves a unique public port for the purpose of forwarding to the given target,
+     * associated with a given location for subsequent lookup purpose.
+     * <p>
+     * If already allocated, returns the previously allocated.
+     * 
+     * @deprecated since 0.7.0; use {@link #acquirePublicPort(String)}, and then use {@link #associate(String, HostAndPort, int)} or {@link #associate(String, HostAndPort, Location, int)}
+     */
+    @Override
+    @Deprecated
+    public int acquirePublicPort(String publicIpId, Location l, int privatePort) {
+        return getDelegate().acquirePublicPort(publicIpId, l, privatePort);
+    }
+
+    /** 
+     * Returns old mapping if it existed, null if it is new.
+     * 
+     * @deprecated since 0.7.0; use {@link #associate(String, HostAndPort, int)} or {@link #associate(String, HostAndPort, Location, int)}
+     */
+    @Override
+    @Deprecated
+    public PortMapping acquirePublicPortExplicit(String publicIpId, int publicPort) {
+        return getDelegate().acquirePublicPortExplicit(publicIpId, publicPort);
+    }
+
+    /**
+     * Records a location and private port against a publicIp and public port,
+     * to support {@link #lookup(Location, int)}.
+     * <p>
+     * Superfluous if {@link #acquirePublicPort(String, Location, int)} was used,
+     * but strongly recommended if {@link #acquirePublicPortExplicit(String, int)} was used
+     * e.g. if the location is not known ahead of time.
+     * 
+     * @deprecated Use {@link #associate(String, HostAndPort, Location, int)}
+     */
+    @Override
+    @Deprecated
+    public void associate(String publicIpId, int publicPort, Location l, int privatePort) {
+        getDelegate().associate(publicIpId, publicPort, l, privatePort);
+    }
+
+    /**
+     * Records a public hostname or address to be associated with the given publicIpId for lookup purposes.
+     * <p>
+     * Conceivably this may have to be access-location specific.
+     * 
+     * @deprecated Use {@link #associate(String, HostAndPort, int)} or {@link #associate(String, HostAndPort, Location, int)}
+     */
+    @Override
+    @Deprecated
+    public void recordPublicIpHostname(String publicIpId, String hostnameOrPublicIpAddress) {
+        getDelegate().recordPublicIpHostname(publicIpId, hostnameOrPublicIpAddress);
+    }
+
+    /**
+     * Returns a recorded public hostname or address.
+     * 
+     * @deprecated Use {@link #lookup(String, int)} or {@link #lookup(Location, int)}
+     */
+    @Override
+    @Deprecated
+    public String getPublicIpHostname(String publicIpId) {
+        return getDelegate().getPublicIpHostname(publicIpId);
+    }
+    
+    /**
+     * Clears a previous call to {@link #recordPublicIpHostname(String, String)}.
+     * 
+     * @deprecated Use {@link #forgetPortMapping(String, int)} or {@link #forgetPortMapping(Location, int)}
+     */
+    @Override
+    @Deprecated
+    public boolean forgetPublicIpHostname(String publicIpId) {
+        return getDelegate().forgetPublicIpHostname(publicIpId);
+    }
+
+    @Override
+    @Deprecated
+    public boolean isClient() {
+        return true;
+    }
+
+
+    ///////////////////////////////////////////////////////////////////////////////////
+    // Deprecated; just internal
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    /** 
+     * Returns the port mapping for a given publicIpId and public port.
+     * 
+     * @deprecated since 0.7.0; this method will be internal only
+     */
+    @Override
+    @Deprecated
+    public PortMapping getPortMappingWithPublicSide(String publicIpId, int publicPort) {
+        return getDelegate().getPortMappingWithPublicSide(publicIpId, publicPort);
+    }
+
+    /** 
+     * Returns the subset of port mappings associated with a given public IP ID.
+     * 
+     * @deprecated since 0.7.0; this method will be internal only
+     */
+    @Override
+    @Deprecated
+    public Collection<PortMapping> getPortMappingWithPublicIpId(String publicIpId) {
+        return getDelegate().getPortMappingWithPublicIpId(publicIpId);
+    }
+
+    /** 
+     * @see #forgetPortMapping(String, int)
+     * 
+     * @deprecated since 0.7.0; this method will be internal only
+     */
+    @Override
+    @Deprecated
+    public boolean forgetPortMapping(PortMapping m) {
+        return getDelegate().forgetPortMapping(m);
+    }
+
+    /**
+     * Returns the public host and port for use accessing the given mapping.
+     * <p>
+     * Conceivably this may have to be access-location specific.
+     * 
+     * @deprecated since 0.7.0; this method will be internal only
+     */
+    @Override
+    @Deprecated
+    public HostAndPort getPublicHostAndPort(PortMapping m) {
+        return getDelegate().getPublicHostAndPort(m);
+    }
+
+    /** 
+     * Returns the subset of port mappings associated with a given location.
+     * 
+     * @deprecated since 0.7.0; this method will be internal only
+     */
+    @Override
+    @Deprecated
+    public Collection<PortMapping> getLocationPublicIpIds(Location l) {
+        return getDelegate().getLocationPublicIpIds(l);
+    }
+        
+    /** 
+     * Returns the mapping to a given private port, or null if none.
+     * 
+     * @deprecated since 0.7.0; this method will be internal only
+     */
+    @Override
+    @Deprecated
+    public PortMapping getPortMappingWithPrivateSide(Location l, int privatePort) {
+        return getDelegate().getPortMappingWithPrivateSide(l, privatePort);
+    }
+    
+    @Override
+    public String toString() {
+        return getClass().getName()+"[id="+getId()+"]";
+    }
+
+    @Override
+    public String getDisplayName() {
+        return getDelegate().getDisplayName();
+    }
+
+    @Override
+    public Location getParent() {
+        return getDelegate().getParent();
+    }
+
+    @Override
+    public Collection<Location> getChildren() {
+        return getDelegate().getChildren();
+    }
+
+    @Override
+    public void setParent(Location newParent) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean containsLocation(Location potentialDescendent) {
+        return getDelegate().containsLocation(potentialDescendent);
+    }
+
+    @Override
+    public <T> T getConfig(ConfigKey<T> key) {
+        return getDelegate().getConfig(key);
+    }
+
+    @Override
+    public <T> T getConfig(HasConfigKey<T> key) {
+        return getDelegate().getConfig(key);
+    }
+
+    @Override
+    public boolean hasConfig(ConfigKey<?> key, boolean includeInherited) {
+        return getDelegate().hasConfig(key, includeInherited);
+    }
+
+    @Override
+    public Map<String, Object> getAllConfig(boolean includeInherited) {
+        return getDelegate().getAllConfig(includeInherited);
+    }
+
+    @Override
+    public boolean hasExtension(Class<?> extensionType) {
+        return getDelegate().hasExtension(extensionType);
+    }
+
+    @Override
+    public <T> T getExtension(Class<T> extensionType) {
+        return getDelegate().getExtension(extensionType);
+    }
+
+    @Override
+    public String getCatalogItemId() {
+        return getDelegate().getCatalogItemId();
+    }
+
+    @Override
+    public TagSupport tags() {
+        return getDelegate().tags();
+    }
+
+    @Override
+    public <T> T setConfig(ConfigKey<T> key, T val) {
+        return getDelegate().setConfig(key, val);
+    }
+
+    @Override
+    public ConfigurationSupport config() {
+        return getDelegate().config();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerImpl.java b/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerImpl.java
new file mode 100644
index 0000000..6493f30
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerImpl.java
@@ -0,0 +1,506 @@
+/*
+ * 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.access;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.brooklyn.api.entity.rebind.RebindContext;
+import org.apache.brooklyn.api.entity.rebind.RebindSupport;
+import org.apache.brooklyn.location.Location;
+import org.apache.brooklyn.location.basic.AbstractLocation;
+import org.apache.brooklyn.mementos.LocationMemento;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.rebind.BasicLocationRebindSupport;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.exceptions.Exceptions;
+
+import com.google.common.base.Objects.ToStringHelper;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.net.HostAndPort;
+
+/**
+ * 
+ * @author aled
+ *
+ * TODO This implementation is not efficient, and currently has a cap of about 50000 rules.
+ * Need to improve the efficiency and scale.
+ * A quick win could be to use a different portReserved counter for each publicIpId,
+ * when calling acquirePublicPort?
+ * 
+ * TODO Callers need to be more careful in acquirePublicPort for which ports are actually in use.
+ * If multiple apps sharing the same public-ip (e.g. in the same vcloud-director vOrg) then they 
+ * must not allocate the same public port (e.g. ensure they share the same PortForwardManager
+ * by using the same scope in 
+ * {@code managementContext.getLocationRegistry().resolve("portForwardManager(scope=global)")}.
+ * However, this still doesn't check if the port is *actually* available. For example, if a
+ * different Brooklyn instance is also deploying there then we can get port conflicts, or if 
+ * some ports in that range are already in use (e.g. due to earlier dev/test runs) then this
+ * will not be respected. Callers should probably figure out the port number themselves, but
+ * that also leads to concurrency issues.
+ * 
+ * TODO The publicIpId means different things to different callers:
+ * <ul>
+ *   <li> In acquirePublicPort() it is (often?) an identifier of the actual public ip.
+ *   <li> In later calls to associate(), it is (often?) an identifier for the target machine
+ *        such as the jcloudsMachine.getJcloudsId().
+ * </ul>
+ */
+@SuppressWarnings("serial")
+public class PortForwardManagerImpl extends AbstractLocation implements PortForwardManager {
+
+    private static final Logger log = LoggerFactory.getLogger(PortForwardManagerImpl.class);
+    
+    protected final Map<String,PortMapping> mappings = new LinkedHashMap<String,PortMapping>();
+
+    private final Map<AssociationListener, Predicate<? super AssociationMetadata>> associationListeners = new ConcurrentHashMap<AssociationListener, Predicate<? super AssociationMetadata>>();
+
+    @Deprecated
+    protected final Map<String,String> publicIpIdToHostname = new LinkedHashMap<String,String>();
+    
+    // horrible hack -- see javadoc above
+    private final AtomicInteger portReserved = new AtomicInteger(11000);
+
+    private final Object mutex = new Object();
+    
+    public PortForwardManagerImpl() {
+        super();
+        if (isLegacyConstruction()) {
+            log.warn("Deprecated construction of "+PortForwardManagerImpl.class.getName()+"; instead use location resolver");
+        }
+    }
+    
+    @Override
+    public void init() {
+        super.init();
+        Integer portStartingPoint;
+        Object rawPort = getAllConfigBag().getStringKey(PORT_FORWARD_MANAGER_STARTING_PORT.getName());
+        if (rawPort != null) {
+            portStartingPoint = getConfig(PORT_FORWARD_MANAGER_STARTING_PORT);
+        } else {
+            portStartingPoint = getManagementContext().getConfig().getConfig(PORT_FORWARD_MANAGER_STARTING_PORT);
+        }
+        portReserved.set(portStartingPoint);
+        log.debug(this+" set initial port to "+portStartingPoint);
+    }
+
+    // TODO Need to use attributes for these so they are persisted (once a location is an entity),
+    // rather than this deprecated approach of custom fields.
+    @Override
+    public RebindSupport<LocationMemento> getRebindSupport() {
+        return new BasicLocationRebindSupport(this) {
+            @Override public LocationMemento getMemento() {
+                Map<String, PortMapping> mappingsCopy;
+                Map<String,String> publicIpIdToHostnameCopy;
+                synchronized (mutex) {
+                    mappingsCopy = MutableMap.copyOf(mappings);
+                    publicIpIdToHostnameCopy = MutableMap.copyOf(publicIpIdToHostname);
+                }
+                return getMementoWithProperties(MutableMap.<String,Object>of(
+                        "mappings", mappingsCopy, 
+                        "portReserved", portReserved.get(), 
+                        "publicIpIdToHostname", publicIpIdToHostnameCopy));
+            }
+            @Override
+            protected void doReconstruct(RebindContext rebindContext, LocationMemento memento) {
+                super.doReconstruct(rebindContext, memento);
+                mappings.putAll( Preconditions.checkNotNull((Map<String, PortMapping>) memento.getCustomField("mappings"), "mappings was not serialized correctly"));
+                portReserved.set( (Integer)memento.getCustomField("portReserved"));
+                publicIpIdToHostname.putAll( Preconditions.checkNotNull((Map<String, String>)memento.getCustomField("publicIpIdToHostname"), "publicIpIdToHostname was not serialized correctly") );
+            }
+        };
+    }
+    
+    @Override
+    public int acquirePublicPort(String publicIpId) {
+        int port;
+        synchronized (mutex) {
+            // far too simple -- see javadoc above
+            port = getNextPort();
+            
+            // TODO When delete deprecated code, stop registering PortMapping until associate() is called
+            PortMapping mapping = new PortMapping(publicIpId, port, null, -1);
+            log.debug(this+" allocating public port "+port+" on "+publicIpId+" (no association info yet)");
+            
+            mappings.put(makeKey(publicIpId, port), mapping);
+        }
+        onChanged();
+        return port;
+    }
+
+    protected int getNextPort() {
+        // far too simple -- see javadoc above
+        return portReserved.getAndIncrement();
+    }
+    
+    @Override
+    public void associate(String publicIpId, HostAndPort publicEndpoint, Location l, int privatePort) {
+        associateImpl(publicIpId, publicEndpoint, l, privatePort);
+        emitAssociationCreatedEvent(publicIpId, publicEndpoint, l, privatePort);
+    }
+
+    @Override
+    public void associate(String publicIpId, HostAndPort publicEndpoint, int privatePort) {
+        associateImpl(publicIpId, publicEndpoint, null, privatePort);
+        emitAssociationCreatedEvent(publicIpId, publicEndpoint, null, privatePort);
+    }
+
+    protected void associateImpl(String publicIpId, HostAndPort publicEndpoint, Location l, int privatePort) {
+        synchronized (mutex) {
+            String publicIp = publicEndpoint.getHostText();
+            int publicPort = publicEndpoint.getPort();
+            recordPublicIpHostname(publicIpId, publicIp);
+            PortMapping mapping = new PortMapping(publicIpId, publicEndpoint, l, privatePort);
+            PortMapping oldMapping = getPortMappingWithPublicSide(publicIpId, publicPort);
+            log.debug(this+" associating public "+publicEndpoint+" on "+publicIpId+" with private port "+privatePort+" at "+l+" ("+mapping+")"
+                    +(oldMapping == null ? "" : " (overwriting "+oldMapping+" )"));
+            mappings.put(makeKey(publicIpId, publicPort), mapping);
+        }
+        onChanged();
+    }
+
+    private void emitAssociationCreatedEvent(String publicIpId, HostAndPort publicEndpoint, Location location, int privatePort) {
+        AssociationMetadata metadata = new AssociationMetadata(publicIpId, publicEndpoint, location, privatePort);
+        for (Map.Entry<AssociationListener, Predicate<? super AssociationMetadata>> entry : associationListeners.entrySet()) {
+            if (entry.getValue().apply(metadata)) {
+                try {
+                    entry.getKey().onAssociationCreated(metadata);
+                } catch (Exception e) {
+                    Exceptions.propagateIfFatal(e);
+                    log.warn("Exception thrown when emitting association creation event " + metadata, e);
+                }
+            }
+        }
+    }
+
+    @Override
+    public HostAndPort lookup(Location l, int privatePort) {
+        synchronized (mutex) {
+            for (PortMapping m: mappings.values()) {
+                if (l.equals(m.target) && privatePort == m.privatePort)
+                    return getPublicHostAndPort(m);
+            }
+        }
+        return null;
+    }
+    
+    @Override
+    public HostAndPort lookup(String publicIpId, int privatePort) {
+        synchronized (mutex) {
+            for (PortMapping m: mappings.values()) {
+                if (publicIpId.equals(m.publicIpId) && privatePort==m.privatePort)
+                    return getPublicHostAndPort(m);
+            }
+        }
+        return null;
+    }
+    
+    @Override
+    public boolean forgetPortMapping(String publicIpId, int publicPort) {
+        PortMapping old;
+        synchronized (mutex) {
+            old = mappings.remove(makeKey(publicIpId, publicPort));
+            if (old != null) {
+                emitAssociationDeletedEvent(associationMetadataFromPortMapping(old));
+            }
+            log.debug("cleared port mapping for "+publicIpId+":"+publicPort+" - "+old);
+        }
+        if (old != null) onChanged();
+        return (old != null);
+    }
+    
+    @Override
+    public boolean forgetPortMappings(Location l) {
+        List<PortMapping> result = Lists.newArrayList();
+        synchronized (mutex) {
+            for (Iterator<PortMapping> iter = mappings.values().iterator(); iter.hasNext();) {
+                PortMapping m = iter.next();
+                if (l.equals(m.target)) {
+                    iter.remove();
+                    result.add(m);
+                    emitAssociationDeletedEvent(associationMetadataFromPortMapping(m));
+                }
+            }
+        }
+        if (log.isDebugEnabled()) log.debug("cleared all port mappings for "+l+" - "+result);
+        if (!result.isEmpty()) {
+            onChanged();
+        }
+        return !result.isEmpty();
+    }
+    
+    @Override
+    public boolean forgetPortMappings(String publicIpId) {
+        List<PortMapping> result = Lists.newArrayList();
+        synchronized (mutex) {
+            for (Iterator<PortMapping> iter = mappings.values().iterator(); iter.hasNext();) {
+                PortMapping m = iter.next();
+                if (publicIpId.equals(m.publicIpId)) {
+                    iter.remove();
+                    result.add(m);
+                    emitAssociationDeletedEvent(associationMetadataFromPortMapping(m));
+                }
+            }
+        }
+        if (log.isDebugEnabled()) log.debug("cleared all port mappings for "+publicIpId+" - "+result);
+        if (!result.isEmpty()) {
+            onChanged();
+        }
+        return !result.isEmpty();
+    }
+
+    private void emitAssociationDeletedEvent(AssociationMetadata metadata) {
+        for (Map.Entry<AssociationListener, Predicate<? super AssociationMetadata>> entry : associationListeners.entrySet()) {
+            if (entry.getValue().apply(metadata)) {
+                try {
+                    entry.getKey().onAssociationDeleted(metadata);
+                } catch (Exception e) {
+                    Exceptions.propagateIfFatal(e);
+                    log.warn("Exception thrown when emitting association creation event " + metadata, e);
+                }
+            }
+        }
+    }
+    
+    @Override
+    protected ToStringHelper string() {
+        int size;
+        synchronized (mutex) {
+            size = mappings.size();
+        }
+        return super.string().add("scope", getScope()).add("mappingsSize", size);
+    }
+
+    @Override
+    public String toVerboseString() {
+        String mappingsStr;
+        synchronized (mutex) {
+            mappingsStr = mappings.toString();
+        }
+        return string().add("mappings", mappingsStr).toString();
+    }
+
+    @Override
+    public String getScope() {
+        return checkNotNull(getConfig(SCOPE), "scope");
+    }
+
+    @Override
+    public boolean isClient() {
+        return false;
+    }
+
+    @Override
+    public void addAssociationListener(AssociationListener listener, Predicate<? super AssociationMetadata> filter) {
+        associationListeners.put(listener, filter);
+    }
+
+    @Override
+    public void removeAssociationListener(AssociationListener listener) {
+        associationListeners.remove(listener);
+    }
+
+    protected String makeKey(String publicIpId, int publicPort) {
+        return publicIpId+":"+publicPort;
+    }
+
+    private AssociationMetadata associationMetadataFromPortMapping(PortMapping portMapping) {
+        String publicIpId = portMapping.getPublicEndpoint().getHostText();
+        HostAndPort publicEndpoint = portMapping.getPublicEndpoint();
+        Location location = portMapping.getTarget();
+        int privatePort = portMapping.getPrivatePort();
+        return new AssociationMetadata(publicIpId, publicEndpoint, location, privatePort);
+    }
+    
+    ///////////////////////////////////////////////////////////////////////////////////
+    // Internal state, for generating memento
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    public List<PortMapping> getPortMappings() {
+        synchronized (mutex) {
+            return ImmutableList.copyOf(mappings.values());
+        }
+    }
+    
+    public Map<String, Integer> getPortCounters() {
+        return ImmutableMap.of("global", portReserved.get());
+    }
+
+    
+    ///////////////////////////////////////////////////////////////////////////////////
+    // Deprecated
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    @Deprecated
+    public PortMapping acquirePublicPortExplicit(String publicIpId, int port) {
+        PortMapping mapping = new PortMapping(publicIpId, port, null, -1);
+        log.debug("assigning explicit public port "+port+" at "+publicIpId);
+        PortMapping result;
+        synchronized (mutex) {
+            result = mappings.put(makeKey(publicIpId, port), mapping);
+        }
+        onChanged();
+        return result;
+    }
+
+    @Override
+    @Deprecated
+    public boolean forgetPortMapping(PortMapping m) {
+        return forgetPortMapping(m.publicIpId, m.publicPort);
+    }
+
+    @Override
+    @Deprecated
+    public void recordPublicIpHostname(String publicIpId, String hostnameOrPublicIpAddress) {
+        log.debug("recording public IP "+publicIpId+" associated with "+hostnameOrPublicIpAddress);
+        synchronized (mutex) {
+            String old = publicIpIdToHostname.put(publicIpId, hostnameOrPublicIpAddress);
+            if (old!=null && !old.equals(hostnameOrPublicIpAddress))
+                log.warn("Changing hostname recorded against public IP "+publicIpId+"; from "+old+" to "+hostnameOrPublicIpAddress);
+        }
+        onChanged();
+    }
+
+    @Override
+    @Deprecated
+    public String getPublicIpHostname(String publicIpId) {
+        synchronized (mutex) {
+            return publicIpIdToHostname.get(publicIpId);
+        }
+    }
+    
+    @Override
+    @Deprecated
+    public boolean forgetPublicIpHostname(String publicIpId) {
+        log.debug("forgetting public IP "+publicIpId+" association");
+        boolean result;
+        synchronized (mutex) {
+            result = (publicIpIdToHostname.remove(publicIpId) != null);
+        }
+        onChanged();
+        return result;
+    }
+
+    @Override
+    @Deprecated
+    public int acquirePublicPort(String publicIpId, Location l, int privatePort) {
+        int publicPort;
+        synchronized (mutex) {
+            PortMapping old = getPortMappingWithPrivateSide(l, privatePort);
+            // only works for 1 public IP ID per location (which is the norm)
+            if (old!=null && old.publicIpId.equals(publicIpId)) {
+                log.debug("request to acquire public port at "+publicIpId+" for "+l+":"+privatePort+", reusing old assignment "+old);
+                return old.getPublicPort();
+            }
+            
+            publicPort = acquirePublicPort(publicIpId);
+            log.debug("request to acquire public port at "+publicIpId+" for "+l+":"+privatePort+", allocating "+publicPort);
+            associateImpl(publicIpId, publicPort, l, privatePort);
+        }
+        onChanged();
+        return publicPort;
+    }
+
+    @Override
+    @Deprecated
+    public void associate(String publicIpId, int publicPort, Location l, int privatePort) {
+        synchronized (mutex) {
+            associateImpl(publicIpId, publicPort, l, privatePort);
+        }
+        onChanged();
+    }
+
+    protected void associateImpl(String publicIpId, int publicPort, Location l, int privatePort) {
+        synchronized (mutex) {
+            PortMapping mapping = new PortMapping(publicIpId, publicPort, l, privatePort);
+            PortMapping oldMapping = getPortMappingWithPublicSide(publicIpId, publicPort);
+            log.debug("associating public port "+publicPort+" on "+publicIpId+" with private port "+privatePort+" at "+l+" ("+mapping+")"
+                    +(oldMapping == null ? "" : " (overwriting "+oldMapping+" )"));
+            mappings.put(makeKey(publicIpId, publicPort), mapping);
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////
+    // Internal only; make protected when deprecated interface method removed
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    public HostAndPort getPublicHostAndPort(PortMapping m) {
+        if (m.publicEndpoint == null) {
+            String hostname = getPublicIpHostname(m.publicIpId);
+            if (hostname==null)
+                throw new IllegalStateException("No public hostname associated with "+m.publicIpId+" (mapping "+m+")");
+            return HostAndPort.fromParts(hostname, m.publicPort);
+        } else {
+            return m.publicEndpoint;
+        }
+    }
+
+    @Override
+    public PortMapping getPortMappingWithPublicSide(String publicIpId, int publicPort) {
+        synchronized (mutex) {
+            return mappings.get(makeKey(publicIpId, publicPort));
+        }
+    }
+
+    @Override
+    public Collection<PortMapping> getPortMappingWithPublicIpId(String publicIpId) {
+        List<PortMapping> result = new ArrayList<PortMapping>();
+        synchronized (mutex) {
+            for (PortMapping m: mappings.values())
+                if (publicIpId.equals(m.publicIpId)) result.add(m);
+        }
+        return result;
+    }
+
+    /** returns the subset of port mappings associated with a given location */
+    @Override
+    public Collection<PortMapping> getLocationPublicIpIds(Location l) {
+        List<PortMapping> result = new ArrayList<PortMapping>();
+        synchronized (mutex) {
+            for (PortMapping m: mappings.values())
+                if (l.equals(m.getTarget())) result.add(m);
+        }
+        return result;
+    }
+
+    @Override
+    public PortMapping getPortMappingWithPrivateSide(Location l, int privatePort) {
+        synchronized (mutex) {
+            for (PortMapping m: mappings.values())
+                if (l.equals(m.getTarget()) && privatePort==m.privatePort) return m;
+        }
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerLocationResolver.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerLocationResolver.java b/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerLocationResolver.java
new file mode 100644
index 0000000..dcb9048
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/location/access/PortForwardManagerLocationResolver.java
@@ -0,0 +1,90 @@
+/*
+ * 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.access;
+
+import java.util.Map;
+
+import org.apache.brooklyn.location.Location;
+import org.apache.brooklyn.location.LocationRegistry;
+import org.apache.brooklyn.location.LocationSpec;
+import org.apache.brooklyn.location.basic.LocationConfigUtils;
+import org.apache.brooklyn.location.basic.LocationInternal;
+import org.apache.brooklyn.location.basic.LocationPredicates;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.brooklyn.location.basic.AbstractLocationResolver;
+import brooklyn.util.config.ConfigBag;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicates;
+import com.google.common.collect.Iterables;
+
+public class PortForwardManagerLocationResolver extends AbstractLocationResolver {
+
+    private static final Logger LOG = LoggerFactory.getLogger(PortForwardManagerLocationResolver.class);
+
+    public static final String PREFIX = "portForwardManager";
+
+    @Override
+    public String getPrefix() {
+        return PREFIX;
+    }
+
+    @Override
+    public Location newLocationFromString(Map locationFlags, String spec, LocationRegistry registry) {
+        ConfigBag config = extractConfig(locationFlags, spec, registry);
+        Map globalProperties = registry.getProperties();
+        String namedLocation = (String) locationFlags.get(LocationInternal.NAMED_SPEC_NAME.getName());
+        String scope = config.get(PortForwardManager.SCOPE);
+
+        Optional<Location> result = Iterables.tryFind(managementContext.getLocationManager().getLocations(), 
+                Predicates.and(
+                        Predicates.instanceOf(PortForwardManager.class), 
+                        LocationPredicates.configEqualTo(PortForwardManager.SCOPE, scope)));
+        
+        if (result.isPresent()) {
+            return result.get();
+        } else {
+            PortForwardManager loc = managementContext.getLocationManager().createLocation(LocationSpec.create(PortForwardManagerImpl.class)
+                    .configure(config.getAllConfig())
+                    .configure(LocationConfigUtils.finalAndOriginalSpecs(spec, locationFlags, globalProperties, namedLocation)));
+            
+            if (LOG.isDebugEnabled()) LOG.debug("Created "+loc+" for scope "+scope);
+            return loc;
+        }
+    }
+
+    @Override
+    protected Class<? extends Location> getLocationType() {
+        return PortForwardManager.class;
+    }
+
+    @Override
+    protected SpecParser getSpecParser() {
+        return new AbstractLocationResolver.SpecParser(getPrefix()).setExampleUsage("\"portForwardManager\" or \"portForwardManager(scope=global)\"");
+    }
+    
+    @Override
+    protected ConfigBag extractConfig(Map<?,?> locationFlags, String spec, LocationRegistry registry) {
+        ConfigBag config = super.extractConfig(locationFlags, spec, registry);
+        config.putAsStringKeyIfAbsent("name", "localhost");
+        return config;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/access/PortMapping.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/access/PortMapping.java b/core/src/main/java/org/apache/brooklyn/location/access/PortMapping.java
new file mode 100644
index 0000000..086b67c
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/location/access/PortMapping.java
@@ -0,0 +1,101 @@
+/*
+ * 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.access;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.location.Location;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Objects;
+import com.google.common.net.HostAndPort;
+
+public class PortMapping {
+
+    final String publicIpId;
+    final HostAndPort publicEndpoint;
+    final int publicPort;
+
+    final Location target;
+    final int privatePort;
+    // TODO CIDR's ?
+
+    public PortMapping(String publicIpId, HostAndPort publicEndpoint, Location target, int privatePort) {
+        this.publicIpId = checkNotNull(publicIpId, "publicIpId");
+        this.publicEndpoint = checkNotNull(publicEndpoint, "publicEndpoint");
+        this.publicPort = publicEndpoint.getPort();
+        this.target = target;
+        this.privatePort = privatePort;
+    }
+    
+    public PortMapping(String publicIpId, int publicPort, Location target, int privatePort) {
+        this.publicIpId = checkNotNull(publicIpId, "publicIpId");
+        this.publicEndpoint = null;
+        this.publicPort = publicPort;
+        this.target = target;
+        this.privatePort = privatePort;
+    }
+
+    // In a release after 0.7.0, this will no longer be @Nullable
+    @Beta
+    @Nullable
+    public HostAndPort getPublicEndpoint() {
+        return publicEndpoint;
+    }
+
+    public int getPublicPort() {
+        return publicPort;
+    }
+
+    public Location getTarget() {
+        return target;
+    }
+    
+    public int getPrivatePort() {
+        return privatePort;
+    }
+    
+    @Override
+    public String toString() {
+        return Objects.toStringHelper(this)
+                .add("publicIpId", publicIpId+":"+publicPort)
+                .add("publicEndpoint", (publicEndpoint == null ? publicPort : publicEndpoint))
+                .add("targetLocation", target)
+                .add("targetPort", privatePort)
+                .toString();
+    }
+    
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof PortMapping)) return false;
+        PortMapping opm = (PortMapping)obj;
+        return Objects.equal(publicIpId, opm.publicIpId) &&
+            Objects.equal(publicPort, opm.publicPort) &&
+            Objects.equal(target, opm.target) &&
+            Objects.equal(privatePort, opm.privatePort);
+    }
+    
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(publicIpId, publicPort, target, privatePort);
+    }
+    
+}
\ No newline at end of file