You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by al...@apache.org on 2015/07/31 16:04:40 UTC

[2/3] incubator-brooklyn git commit: BYON: machine gets extra conf for duration of use

BYON: machine gets extra conf for duration of use

When calling `byon.obtain(customConfig)`, the returned machine will
have the given custom config. When the machine is released, the
config will be restored to what it was before.

Adds ConfigSupportInternal.removeFromLocalBag(String key)

Adds DoNothingSoftwareProcess, for testing


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/3ccd1654
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/3ccd1654
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/3ccd1654

Branch: refs/heads/master
Commit: 3ccd165497f5b448d57a0f1df9ef4863afb5654b
Parents: 9be60ca
Author: Aled Sage <al...@gmail.com>
Authored: Thu Jul 30 23:36:12 2015 +0100
Committer: Aled Sage <al...@gmail.com>
Committed: Fri Jul 31 09:52:39 2015 +0100

----------------------------------------------------------------------
 .../brooklyn/basic/BrooklynObjectInternal.java  |  3 +
 .../brooklyn/entity/basic/AbstractEntity.java   |  5 ++
 .../brooklyn/entity/basic/EntityConfigMap.java  |  5 ++
 .../location/basic/AbstractLocation.java        |  7 +-
 .../FixedListMachineProvisioningLocation.java   | 36 +++++++++++
 .../policy/basic/AbstractEntityAdjunct.java     |  5 ++
 .../brooklyn/policy/basic/ConfigMapImpl.java    | 12 ++--
 .../java/brooklyn/util/config/ConfigBag.java    | 13 ++++
 ...ixedListMachineProvisioningLocationTest.java | 58 ++++++++++++++++-
 .../entity/software/MachineInitTasks.java       |  2 +-
 .../entity/basic/DoNothingSoftwareProcess.java  | 30 +++++++++
 .../basic/DoNothingSoftwareProcessDriver.java   | 67 ++++++++++++++++++++
 .../basic/DoNothingSoftwareProcessImpl.java     | 35 ++++++++++
 .../bind/DoNothingSoftwareProcessDriver.java    |  7 +-
 usage/camp/pom.xml                              |  7 ++
 .../camp/brooklyn/ByonLocationsYamlTest.java    | 36 +++++++++++
 16 files changed, 317 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3ccd1654/core/src/main/java/brooklyn/basic/BrooklynObjectInternal.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/basic/BrooklynObjectInternal.java b/core/src/main/java/brooklyn/basic/BrooklynObjectInternal.java
index 8271b35..81ddc7f 100644
--- a/core/src/main/java/brooklyn/basic/BrooklynObjectInternal.java
+++ b/core/src/main/java/brooklyn/basic/BrooklynObjectInternal.java
@@ -91,6 +91,9 @@ public interface BrooklynObjectInternal extends BrooklynObject, Rebindable {
         void addToLocalBag(Map<String, ?> vals);
 
         @Beta
+        void removeFromLocalBag(String key);
+
+        @Beta
         void refreshInheritedConfig();
         
         @Beta

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3ccd1654/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java b/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
index 507900a..d636e47 100644
--- a/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
+++ b/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
@@ -1044,6 +1044,11 @@ public abstract class AbstractEntity extends AbstractBrooklynObject implements E
         }
 
         @Override
+        public void removeFromLocalBag(String key) {
+            configsInternal.removeFromLocalBag(key);
+        }
+
+        @Override
         public void refreshInheritedConfig() {
             if (getParent() != null) {
                 configsInternal.setInheritedConfig(((EntityInternal)getParent()).getAllConfig(), ((EntityInternal)getParent()).config().getBag());

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3ccd1654/core/src/main/java/brooklyn/entity/basic/EntityConfigMap.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/EntityConfigMap.java b/core/src/main/java/brooklyn/entity/basic/EntityConfigMap.java
index f2821d8..95ce3d0 100644
--- a/core/src/main/java/brooklyn/entity/basic/EntityConfigMap.java
+++ b/core/src/main/java/brooklyn/entity/basic/EntityConfigMap.java
@@ -275,6 +275,11 @@ public class EntityConfigMap extends AbstractConfigMapImpl {
         ownConfig.putAll(localConfigBag.getAllConfigAsConfigKeyMap());
     }
 
+    public void removeFromLocalBag(String key) {
+        localConfigBag.remove(key);
+        ownConfig.remove(key);
+    }
+
     public void clearInheritedConfig() {
         inheritedConfig.clear();
         inheritedConfigBag.clear();

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3ccd1654/core/src/main/java/brooklyn/location/basic/AbstractLocation.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/location/basic/AbstractLocation.java b/core/src/main/java/brooklyn/location/basic/AbstractLocation.java
index 80aa804..3cd35fa 100644
--- a/core/src/main/java/brooklyn/location/basic/AbstractLocation.java
+++ b/core/src/main/java/brooklyn/location/basic/AbstractLocation.java
@@ -442,7 +442,12 @@ public abstract class AbstractLocation extends AbstractBrooklynObject implements
         public void addToLocalBag(Map<String, ?> vals) {
             configBag.putAll(vals);
         }
-        
+
+        @Override
+        public void removeFromLocalBag(String key) {
+            configBag.remove(key);
+        }
+
         @Override
         public void refreshInheritedConfig() {
             // no-op for location

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3ccd1654/core/src/main/java/brooklyn/location/basic/FixedListMachineProvisioningLocation.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/location/basic/FixedListMachineProvisioningLocation.java b/core/src/main/java/brooklyn/location/basic/FixedListMachineProvisioningLocation.java
index 50a2a17..1e2184d 100644
--- a/core/src/main/java/brooklyn/location/basic/FixedListMachineProvisioningLocation.java
+++ b/core/src/main/java/brooklyn/location/basic/FixedListMachineProvisioningLocation.java
@@ -98,6 +98,9 @@ implements MachineProvisioningLocation<T>, Closeable {
     @SetFromFlag
     protected Set<T> pendingRemoval;
     
+    @SetFromFlag
+    protected Map<T, Map<String, Object>> origConfigs;
+
     public FixedListMachineProvisioningLocation() {
         this(Maps.newLinkedHashMap());
     }
@@ -143,6 +146,7 @@ implements MachineProvisioningLocation<T>, Closeable {
         if (machines == null) machines = Sets.newLinkedHashSet();
         if (inUse == null) inUse = Sets.newLinkedHashSet();
         if (pendingRemoval == null) pendingRemoval = Sets.newLinkedHashSet();
+        if (origConfigs == null) origConfigs = Maps.newLinkedHashMap();
         return super.configure(properties);
     }
     
@@ -271,6 +275,7 @@ implements MachineProvisioningLocation<T>, Closeable {
                 }
             }
             inUse.add(machine);
+            updateMachineConfig(machine, flags);
         }
         
         for (MachineLocationCustomizer customizer : getMachineCustomizers(allflags)) {
@@ -290,6 +295,7 @@ implements MachineProvisioningLocation<T>, Closeable {
         synchronized (lock) {
             if (inUse.contains(machine) == false)
                 throw new IllegalStateException("Request to release machine "+machine+", but this machine is not currently allocated");
+            restoreMachineConfig(machine);
             inUse.remove(machine);
             
             if (pendingRemoval.contains(machine)) {
@@ -303,6 +309,36 @@ implements MachineProvisioningLocation<T>, Closeable {
         return Maps.<String,Object>newLinkedHashMap();
     }
     
+    protected void updateMachineConfig(T machine, Map<?, ?> flags) {
+        if (origConfigs == null) {
+            // For backwards compatibility, where peristed state did not have this.
+            origConfigs = Maps.newLinkedHashMap();
+        }
+        Map<String, Object> strFlags = ConfigBag.newInstance(flags).getAllConfig();
+        Map<String, Object> origConfig = ((ConfigurationSupportInternal)machine.config()).getLocalBag().getAllConfig();
+        origConfigs.put(machine, origConfig);
+        requestPersist();
+        
+        ((ConfigurationSupportInternal)machine.config()).addToLocalBag(strFlags);
+    }
+    
+    protected void restoreMachineConfig(MachineLocation machine) {
+        if (origConfigs == null) {
+            // For backwards compatibility, where peristed state did not have this.
+            origConfigs = Maps.newLinkedHashMap();
+        }
+        Map<String, Object> origConfig = origConfigs.remove(machine);
+        if (origConfig == null) return;
+        requestPersist();
+        
+        Set<String> currentKeys = ((ConfigurationSupportInternal)machine.config()).getLocalBag().getAllConfig().keySet();
+        Set<String> newKeys = Sets.difference(currentKeys, origConfig.entrySet());
+        for (String key : newKeys) {
+            ((ConfigurationSupportInternal)machine.config()).removeFromLocalBag(key);
+        }
+        ((ConfigurationSupportInternal)machine.config()).addToLocalBag(origConfig);
+    }
+    
     @SuppressWarnings("unchecked")
     private <K> K getConfigPreferringOverridden(ConfigKey<K> key, Map<?,?> overrides) {
         K result = (K) overrides.get(key);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3ccd1654/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java b/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
index b14fd3e..1b91ce9 100644
--- a/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
+++ b/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
@@ -282,6 +282,11 @@ public abstract class AbstractEntityAdjunct extends AbstractBrooklynObject imple
         }
         
         @Override
+        public void removeFromLocalBag(String key) {
+            configsInternal.removeFromLocalBag(key);
+        }
+        
+        @Override
         public void refreshInheritedConfig() {
             // no-op for location
         }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3ccd1654/core/src/main/java/brooklyn/policy/basic/ConfigMapImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/policy/basic/ConfigMapImpl.java b/core/src/main/java/brooklyn/policy/basic/ConfigMapImpl.java
index dc34b17..9f7b958 100644
--- a/core/src/main/java/brooklyn/policy/basic/ConfigMapImpl.java
+++ b/core/src/main/java/brooklyn/policy/basic/ConfigMapImpl.java
@@ -26,6 +26,10 @@ import java.util.Map;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Maps;
+
 import brooklyn.config.ConfigKey;
 import brooklyn.config.internal.AbstractConfigMapImpl;
 import brooklyn.entity.basic.ConfigKeys;
@@ -38,10 +42,6 @@ import brooklyn.util.flags.TypeCoercions;
 import brooklyn.util.guava.Maybe;
 import brooklyn.util.internal.ConfigKeySelfExtracting;
 
-import com.google.common.base.Preconditions;
-import com.google.common.base.Predicate;
-import com.google.common.collect.Maps;
-
 public class ConfigMapImpl extends AbstractConfigMapImpl {
 
     private static final Logger LOG = LoggerFactory.getLogger(ConfigMapImpl.class);
@@ -119,6 +119,10 @@ public class ConfigMapImpl extends AbstractConfigMapImpl {
         }
     }
 
+    public void removeFromLocalBag(String key) {
+        ownConfig.remove(key);
+    }
+
     @Override
     public ConfigMapImpl submap(Predicate<ConfigKey<?>> filter) {
         ConfigMapImpl m = new ConfigMapImpl(adjunct);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3ccd1654/core/src/main/java/brooklyn/util/config/ConfigBag.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/config/ConfigBag.java b/core/src/main/java/brooklyn/util/config/ConfigBag.java
index 606e2fc..2f17748 100644
--- a/core/src/main/java/brooklyn/util/config/ConfigBag.java
+++ b/core/src/main/java/brooklyn/util/config/ConfigBag.java
@@ -354,6 +354,19 @@ public class ConfigBag {
         return getStringKeyMaybe(key, true);
     }
 
+    /** gets a {@link Maybe}-wrapped value from a key, inferring the type of that key (e.g. {@link ConfigKey} or {@link String}) */
+    @Beta
+    public Maybe<Object> getObjKeyMaybe(Object key) {
+        if (key instanceof HasConfigKey<?>) key = ((HasConfigKey<?>)key).getConfigKey();
+        if (key instanceof ConfigKey<?>) key = ((ConfigKey<?>)key).getName();
+        if (key instanceof String) {
+            return getStringKeyMaybe((String)key, true);
+        } else {
+            logInvalidKey(key);
+            return Maybe.absent();
+        }
+    }
+
     /** like get, but without marking it as used */
     public <T> T peek(ConfigKey<T> key) {
         return get(key, false);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3ccd1654/core/src/test/java/brooklyn/location/basic/FixedListMachineProvisioningLocationTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/location/basic/FixedListMachineProvisioningLocationTest.java b/core/src/test/java/brooklyn/location/basic/FixedListMachineProvisioningLocationTest.java
index 78a61a9..bf33ea7 100644
--- a/core/src/test/java/brooklyn/location/basic/FixedListMachineProvisioningLocationTest.java
+++ b/core/src/test/java/brooklyn/location/basic/FixedListMachineProvisioningLocationTest.java
@@ -19,6 +19,7 @@
 package brooklyn.location.basic;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.fail;
 
 import java.net.Inet4Address;
@@ -41,6 +42,8 @@ import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.ConfigKeys;
 import brooklyn.entity.basic.Entities;
 import brooklyn.location.LocationSpec;
 import brooklyn.location.MachineLocation;
@@ -498,8 +501,61 @@ public class FixedListMachineProvisioningLocationTest {
         SshMachineLocation obtained = provisioner.obtain(ImmutableMap.of(FixedListMachineProvisioningLocation.MACHINE_LOCATION_CUSTOMIZERS, ImmutableList.of(customizer)));
         assertEquals(Iterables.getOnlyElement(customizer.calls), new Call("customize", ImmutableList.of(obtained)));
         
-        // TODO Does not call preRelease, because customizer is not config on provisioner, and is not config on machine
         provisioner.release(obtained);
+        assertEquals(customizer.calls.size(), 2);
+        assertEquals(customizer.calls.get(1), new Call("preRelease", ImmutableList.of(obtained)));
+    }
+
+    @Test
+    public void testMachineGivenCustomFlagForDurationOfUsage() throws Exception {
+        boolean origContains = machine.config().getBag().getAllConfig().containsKey("mykey");
+        SshMachineLocation obtained = provisioner.obtain(ImmutableMap.of("mykey", "myNewVal"));
+        Object obtainedVal = obtained.config().getBag().getAllConfig().get("mykey");
+        provisioner.release(obtained);
+        boolean releasedContains = obtained.config().getBag().getAllConfig().containsKey("mykey");
+        
+        assertEquals(obtained, machine);
+        assertFalse(origContains);
+        assertEquals(obtainedVal, "myNewVal");
+        assertFalse(releasedContains);
+    }
+    
+    @Test
+    public void testMachineConfigRestoredToDefaultsOnRelease() throws Exception {
+        ConfigKey<String> mykey = ConfigKeys.newStringConfigKey("mykey");
+        
+        boolean origContains = machine.config().getBag().getAllConfig().containsKey("mykey");
+        SshMachineLocation obtained = provisioner.obtain();
+        obtained.config().set(mykey, "myNewVal");
+        Object obtainedVal = obtained.config().getBag().getAllConfig().get("mykey");
+        
+        provisioner.release(obtained);
+        boolean releasedContains = machine.config().getBag().getAllConfig().containsKey("mykey");
+        releasedContains |= (machine.config().get(mykey) != null);
+        
+        assertEquals(obtained, machine);
+        assertFalse(origContains);
+        assertEquals(obtainedVal, "myNewVal");
+        assertFalse(releasedContains);
+    }
+    
+    @Test
+    public void testMachineGivenOverriddenFlagForDurationOfUsage() throws Exception {
+        SshMachineLocation machine2 = new SshMachineLocation(
+                MutableMap.of("address", Inet4Address.getByName("192.168.144.200"), "mykey", "myval"));
+        provisioner2 = new FixedListMachineProvisioningLocation<SshMachineLocation>(
+                MutableMap.of("machines", MutableList.of(machine2)));
+
+        Object origVal = machine2.config().getBag().getAllConfig().get("mykey");
+        SshMachineLocation obtained = provisioner2.obtain(ImmutableMap.of("mykey", "myNewVal"));
+        Object obtainedVal = obtained.config().getBag().getAllConfig().get("mykey");
+        provisioner2.release(obtained);
+        Object releasedVal = obtained.config().getBag().getAllConfig().get("mykey");
+        
+        assertEquals(obtained, machine2);
+        assertEquals(origVal, "myval");
+        assertEquals(obtainedVal, "myNewVal");
+        assertEquals(releasedVal, "myval");
     }
 
     private static <T> List<T> randomized(Iterable<T> list) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3ccd1654/software/base/src/main/java/brooklyn/entity/software/MachineInitTasks.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/software/MachineInitTasks.java b/software/base/src/main/java/brooklyn/entity/software/MachineInitTasks.java
index dfcb61d..57a5926 100644
--- a/software/base/src/main/java/brooklyn/entity/software/MachineInitTasks.java
+++ b/software/base/src/main/java/brooklyn/entity/software/MachineInitTasks.java
@@ -85,7 +85,7 @@ public class MachineInitTasks {
      * See docs in {@link BashCommands#dontRequireTtyForSudo()}
      */
     public Task<Boolean> dontRequireTtyForSudoAsync(final SshMachineLocation machine) {
-        return SshTasks.dontRequireTtyForSudo(machine, true).newTask().asTask();
+        return DynamicTasks.queue(SshTasks.dontRequireTtyForSudo(machine, true).newTask().asTask());
     }
 
     protected void openIptablesImpl(Iterable<Integer> inboundPorts, SshMachineLocation machine) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3ccd1654/software/base/src/test/java/brooklyn/entity/basic/DoNothingSoftwareProcess.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/brooklyn/entity/basic/DoNothingSoftwareProcess.java b/software/base/src/test/java/brooklyn/entity/basic/DoNothingSoftwareProcess.java
new file mode 100644
index 0000000..19fc1cc
--- /dev/null
+++ b/software/base/src/test/java/brooklyn/entity/basic/DoNothingSoftwareProcess.java
@@ -0,0 +1,30 @@
+/*
+ * 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 brooklyn.entity.basic;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.proxying.ImplementedBy;
+
+@ImplementedBy(DoNothingSoftwareProcessImpl.class)
+public interface DoNothingSoftwareProcess extends SoftwareProcess {
+    
+    public static final ConfigKey<Boolean> SKIP_ON_BOX_BASE_DIR_RESOLUTION = ConfigKeys.newConfigKeyWithDefault(
+            BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION,
+            true);
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3ccd1654/software/base/src/test/java/brooklyn/entity/basic/DoNothingSoftwareProcessDriver.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/brooklyn/entity/basic/DoNothingSoftwareProcessDriver.java b/software/base/src/test/java/brooklyn/entity/basic/DoNothingSoftwareProcessDriver.java
new file mode 100644
index 0000000..aadb898
--- /dev/null
+++ b/software/base/src/test/java/brooklyn/entity/basic/DoNothingSoftwareProcessDriver.java
@@ -0,0 +1,67 @@
+/*
+ * 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 brooklyn.entity.basic;
+
+import brooklyn.location.basic.SshMachineLocation;
+
+/**
+ * Implements methods in {@link brooklyn.entity.basic.AbstractSoftwareProcessSshDriver}
+ * such that no actions are performed.
+ * <p>
+ * {@link #isRunning()} returns true.
+ */
+public class DoNothingSoftwareProcessDriver extends AbstractSoftwareProcessSshDriver {
+
+    public DoNothingSoftwareProcessDriver(EntityLocal entity, SshMachineLocation machine) {
+        super(entity, machine);
+    }
+
+    @Override
+    public boolean isRunning() {
+        return true;
+    }
+
+    @Override
+    public void copyPreInstallResources() {
+    }
+
+    @Override
+    public void copyInstallResources() {
+    }
+
+    @Override
+    public void copyRuntimeResources() {
+    }
+
+    @Override
+    public void install() {
+    }
+
+    @Override
+    public void customize() {
+    }
+
+    @Override
+    public void launch() {
+    }
+
+    @Override
+    public void stop() {
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3ccd1654/software/base/src/test/java/brooklyn/entity/basic/DoNothingSoftwareProcessImpl.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/brooklyn/entity/basic/DoNothingSoftwareProcessImpl.java b/software/base/src/test/java/brooklyn/entity/basic/DoNothingSoftwareProcessImpl.java
new file mode 100644
index 0000000..17cc9fe
--- /dev/null
+++ b/software/base/src/test/java/brooklyn/entity/basic/DoNothingSoftwareProcessImpl.java
@@ -0,0 +1,35 @@
+/*
+ * 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 brooklyn.entity.basic;
+
+public class DoNothingSoftwareProcessImpl extends SoftwareProcessImpl implements DoNothingSoftwareProcess {
+
+    @Override
+    public Class getDriverInterface() {
+        return DoNothingSoftwareProcessDriver.class;
+    }
+    
+    @Override
+    protected void connectSensors() {
+        super.connectSensors();
+        if (getAttribute(SERVICE_STATE_ACTUAL) == Lifecycle.STARTING) {
+            setAttribute(SERVICE_UP, true);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3ccd1654/software/network/src/test/java/brooklyn/entity/network/bind/DoNothingSoftwareProcessDriver.java
----------------------------------------------------------------------
diff --git a/software/network/src/test/java/brooklyn/entity/network/bind/DoNothingSoftwareProcessDriver.java b/software/network/src/test/java/brooklyn/entity/network/bind/DoNothingSoftwareProcessDriver.java
index c49e66b..df7953d 100644
--- a/software/network/src/test/java/brooklyn/entity/network/bind/DoNothingSoftwareProcessDriver.java
+++ b/software/network/src/test/java/brooklyn/entity/network/bind/DoNothingSoftwareProcessDriver.java
@@ -38,10 +38,6 @@ public class DoNothingSoftwareProcessDriver extends AbstractSoftwareProcessSshDr
     }
 
     @Override
-    public void stop() {
-    }
-
-    @Override
     public void install() {
     }
 
@@ -53,4 +49,7 @@ public class DoNothingSoftwareProcessDriver extends AbstractSoftwareProcessSshDr
     public void launch() {
     }
 
+    @Override
+    public void stop() {
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3ccd1654/usage/camp/pom.xml
----------------------------------------------------------------------
diff --git a/usage/camp/pom.xml b/usage/camp/pom.xml
index 5b6c8fc..9a643e1 100644
--- a/usage/camp/pom.xml
+++ b/usage/camp/pom.xml
@@ -120,6 +120,13 @@
         </dependency>
         <dependency>
             <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-software-base</artifactId>
+            <version>${project.version}</version>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
             <artifactId>brooklyn-software-webapp</artifactId>
             <version>${project.version}</version>
             <scope>test</scope>

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3ccd1654/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/ByonLocationsYamlTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/ByonLocationsYamlTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/ByonLocationsYamlTest.java
index 3ddc5ad..eaf3884 100644
--- a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/ByonLocationsYamlTest.java
+++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/ByonLocationsYamlTest.java
@@ -31,6 +31,7 @@ import org.testng.annotations.Test;
 
 import com.google.api.client.repackaged.com.google.common.base.Joiner;
 import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
@@ -38,12 +39,17 @@ import com.google.common.net.HostAndPort;
 
 import brooklyn.entity.Entity;
 import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.DoNothingSoftwareProcess;
+import brooklyn.entity.basic.Entities;
 import brooklyn.location.MachineLocation;
 import brooklyn.location.access.PortForwardManager;
 import brooklyn.location.basic.FixedListMachineProvisioningLocation;
 import brooklyn.location.basic.LocationPredicates;
+import brooklyn.location.basic.Machines;
 import brooklyn.location.basic.SshMachineLocation;
 import brooklyn.location.basic.WinRmMachineLocation;
+import brooklyn.location.cloud.CloudLocationConfig;
+import brooklyn.test.Asserts;
 import brooklyn.util.net.UserAndHostAndPort;
 
 public class ByonLocationsYamlTest extends AbstractYamlTest {
@@ -218,6 +224,36 @@ public class ByonLocationsYamlTest extends AbstractYamlTest {
         assertNull(pfm.lookup(machine2, 12345));
     }
 
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testPassesInboundPortsToMachineAndRemovesOnceMachineReleased() throws Exception {
+        String yaml = Joiner.on("\n").join(
+                "location:",
+                "  byon:",
+                "    hosts:",
+                "    - ssh: 1.1.1.1:22",
+                "      password: mypassword",
+                "      user: myuser",
+                "services:",
+                "- type: brooklyn.entity.basic.DoNothingSoftwareProcess",
+                "  brooklyn.config:",
+                "    requiredOpenLoginPorts: [22, 1024]");
+
+        Entity app = createStartWaitAndLogApplication(new StringReader(yaml));
+        DoNothingSoftwareProcess entity = (DoNothingSoftwareProcess) Iterables.find(Entities.descendants(app), Predicates.instanceOf(DoNothingSoftwareProcess.class));
+        FixedListMachineProvisioningLocation<MachineLocation> loc = (FixedListMachineProvisioningLocation<MachineLocation>) Iterables.get(app.getLocations(), 0);
+        
+        // Machine should have been given the inbound-ports
+        SshMachineLocation machine = Machines.findUniqueSshMachineLocation(entity.getLocations()).get();
+        Asserts.assertEqualsIgnoringOrder((Iterable<?>)machine.config().get(CloudLocationConfig.INBOUND_PORTS), ImmutableList.of(22, 1024));
+        
+        // Stop the entity; should release the machine
+        entity.stop();
+        MachineLocation availableMachine = Iterables.getOnlyElement(loc.getAvailable());
+        assertEquals(availableMachine, machine);
+        assertNull(machine.config().get(CloudLocationConfig.INBOUND_PORTS));
+    }
+
     private void assertMachine(SshMachineLocation machine, UserAndHostAndPort conn, Map<String, ?> config) {
         assertEquals(machine.getAddress().getHostAddress(), conn.getHostAndPort().getHostText());
         assertEquals(machine.getPort(), conn.getHostAndPort().getPort());