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/29 01:13:09 UTC

[5/6] incubator-brooklyn git commit: Adds MACHINE_LOCATION_CUSTOMIZERS

Adds MACHINE_LOCATION_CUSTOMIZERS

A similar idea to the JcloudsMachineLocationCustomizer, but more 
generic - it deals just with the MachineLocation. It can be wired
in to JcloudsLocation and to BYON (i.e. FixedListMachineProvisioningLocation).

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

Branch: refs/heads/master
Commit: bac43b0705f6d2fd51e89402b6631f8f07619dbb
Parents: 90f1881
Author: Aled Sage <al...@gmail.com>
Authored: Mon Jul 27 17:48:02 2015 +0100
Committer: Aled Sage <al...@gmail.com>
Committed: Mon Jul 27 17:48:02 2015 +0100

----------------------------------------------------------------------
 .../BasicMachineLocationCustomizer.java         | 41 +++++++++++
 .../location/MachineLocationCustomizer.java     | 42 ++++++++++++
 .../FixedListMachineProvisioningLocation.java   | 44 +++++++++---
 .../location/cloud/CloudLocationConfig.java     |  8 +++
 ...ixedListMachineProvisioningLocationTest.java | 44 ++++++++++--
 .../RecordingMachineLocationCustomizer.java     | 71 ++++++++++++++++++++
 .../location/jclouds/JcloudsLocation.java       | 71 +++++++++++---------
 .../location/jclouds/JcloudsLocationTest.java   | 10 +++
 8 files changed, 283 insertions(+), 48 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bac43b07/api/src/main/java/brooklyn/location/BasicMachineLocationCustomizer.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/location/BasicMachineLocationCustomizer.java b/api/src/main/java/brooklyn/location/BasicMachineLocationCustomizer.java
new file mode 100644
index 0000000..a05dd44
--- /dev/null
+++ b/api/src/main/java/brooklyn/location/BasicMachineLocationCustomizer.java
@@ -0,0 +1,41 @@
+/*
+ * 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.location;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * A default no-op implementation, which can be extended to override the appropriate methods.
+ * 
+ * Sub-classing will give the user some protection against future API changes - note that 
+ * {@link MachineLocationCustomizer} is marked {@link Beta}.
+ */
+@Beta
+public class BasicMachineLocationCustomizer implements MachineLocationCustomizer {
+
+    @Override
+    public void customize(MachineLocation machine) {
+        // no-op
+    }
+    
+    @Override
+    public void preRelease(MachineLocation machine) {
+        // no-op
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bac43b07/api/src/main/java/brooklyn/location/MachineLocationCustomizer.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/location/MachineLocationCustomizer.java b/api/src/main/java/brooklyn/location/MachineLocationCustomizer.java
new file mode 100644
index 0000000..83e1009
--- /dev/null
+++ b/api/src/main/java/brooklyn/location/MachineLocationCustomizer.java
@@ -0,0 +1,42 @@
+/*
+ * 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.location;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Customization hooks to allow apps to perform specific customisation of obtained machines.
+ * <p>
+ * Users are strongly encouraged to sub-class {@link BasicMachineLocationCustomizer}, to give
+ * some protection against this {@link Beta} API changing in future releases.
+ */
+@Beta
+public interface MachineLocationCustomizer {
+
+    /**
+     * Override to configure the given machine once it has been created (prior to any use).
+     */
+    void customize(MachineLocation machine);
+    
+    /**
+     * Override to handle machine-related cleanup prior to {@link MachineProvisioningLocation} 
+     * releasing the machine.
+     */
+    void preRelease(MachineLocation machine);
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bac43b07/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 d8aad46..50a2a17 100644
--- a/core/src/main/java/brooklyn/location/basic/FixedListMachineProvisioningLocation.java
+++ b/core/src/main/java/brooklyn/location/basic/FixedListMachineProvisioningLocation.java
@@ -31,31 +31,35 @@ import java.util.Set;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Function;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.reflect.TypeToken;
+
 import brooklyn.config.ConfigKey;
 import brooklyn.entity.basic.ConfigKeys;
 import brooklyn.location.Location;
 import brooklyn.location.LocationSpec;
 import brooklyn.location.MachineLocation;
+import brooklyn.location.MachineLocationCustomizer;
 import brooklyn.location.MachineProvisioningLocation;
 import brooklyn.location.NoMachinesAvailableException;
+import brooklyn.location.cloud.CloudLocationConfig;
 import brooklyn.management.LocationManager;
 import brooklyn.util.collections.CollectionFunctionals;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.collections.MutableSet;
+import brooklyn.util.config.ConfigBag;
 import brooklyn.util.flags.SetFromFlag;
 import brooklyn.util.stream.Streams;
 import brooklyn.util.text.WildcardGlobs;
 import brooklyn.util.text.WildcardGlobs.PhraseTreatment;
 
-import com.google.common.base.Function;
-import com.google.common.base.Objects;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-import com.google.common.reflect.TypeToken;
-
 /**
  * A provisioner of {@link MachineLocation}s which takes a list of machines it can connect to.
  * The collection of initial machines should be supplied in the 'machines' flag in the constructor,
@@ -80,6 +84,8 @@ implements MachineProvisioningLocation<T>, Closeable {
                     "byon.machineChooser",
                     "For choosing which of the possible machines is chosen and returned by obtain()",
                     CollectionFunctionals.<MachineLocation>firstElement());
+
+    public static final ConfigKey<Collection<MachineLocationCustomizer>> MACHINE_LOCATION_CUSTOMIZERS = CloudLocationConfig.MACHINE_LOCATION_CUSTOMIZERS;
     
     private final Object lock = new Object();
     
@@ -238,13 +244,14 @@ implements MachineProvisioningLocation<T>, Closeable {
     public T obtain(Map<?,?> flags) throws NoMachinesAvailableException {
         T machine;
         T desiredMachine = (T) flags.get("desiredMachine");
-        Function<Iterable<? extends MachineLocation>, MachineLocation> chooser = getConfigPreferringOverridden(MACHINE_CHOOSER, flags);
+        ConfigBag allflags = ConfigBag.newInstanceExtending(config().getBag()).putAll(flags);
+        Function<Iterable<? extends MachineLocation>, MachineLocation> chooser = allflags.get(MACHINE_CHOOSER);
         
         synchronized (lock) {
             Set<T> a = getAvailable();
             if (a.isEmpty()) {
                 if (canProvisionMore()) {
-                    provisionMore(1, flags);
+                    provisionMore(1, allflags.getAllConfig());
                     a = getAvailable();
                 }
                 if (a.isEmpty())
@@ -265,11 +272,21 @@ implements MachineProvisioningLocation<T>, Closeable {
             }
             inUse.add(machine);
         }
+        
+        for (MachineLocationCustomizer customizer : getMachineCustomizers(allflags)) {
+            customizer.customize(machine);
+        }
+        
         return machine;
     }
 
     @Override
     public void release(T machine) {
+        ConfigBag machineConfig = ((ConfigurationSupportInternal)machine.config()).getBag();
+        for (MachineLocationCustomizer customizer : getMachineCustomizers(machineConfig)) {
+            customizer.preRelease(machine);
+        }
+
         synchronized (lock) {
             if (inUse.contains(machine) == false)
                 throw new IllegalStateException("Request to release machine "+machine+", but this machine is not currently allocated");
@@ -294,6 +311,11 @@ implements MachineProvisioningLocation<T>, Closeable {
         return result;
     }
 
+    protected Collection<MachineLocationCustomizer> getMachineCustomizers(ConfigBag setup) {
+        Collection<MachineLocationCustomizer> customizers = setup.get(MACHINE_LOCATION_CUSTOMIZERS);
+        return (customizers == null ? ImmutableList.<MachineLocationCustomizer>of() : customizers);
+    }
+
     /**
      * Facilitates fluent/programmatic style for constructing a fixed pool of machines.
      * <pre>

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bac43b07/core/src/main/java/brooklyn/location/cloud/CloudLocationConfig.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/location/cloud/CloudLocationConfig.java b/core/src/main/java/brooklyn/location/cloud/CloudLocationConfig.java
index 9134af7..66c6065 100644
--- a/core/src/main/java/brooklyn/location/cloud/CloudLocationConfig.java
+++ b/core/src/main/java/brooklyn/location/cloud/CloudLocationConfig.java
@@ -18,11 +18,15 @@
  */
 package brooklyn.location.cloud;
 
+import java.util.Collection;
+
 import com.google.common.annotations.Beta;
+import com.google.common.reflect.TypeToken;
 
 import brooklyn.config.ConfigKey;
 import brooklyn.entity.basic.ConfigKeys;
 import brooklyn.event.basic.BasicConfigKey;
+import brooklyn.location.MachineLocationCustomizer;
 import brooklyn.location.basic.LocationConfigKeys;
 import brooklyn.util.flags.SetFromFlag;
 
@@ -105,4 +109,8 @@ public interface CloudLocationConfig {
     public static final ConfigKey<String> DOMAIN_NAME = new BasicConfigKey<String>(String.class, "domainName",
         "DNS domain where the host should be created, e.g. yourdomain.com (selected clouds only)", null);
 
+    @SuppressWarnings("serial")
+    public static final ConfigKey<Collection<MachineLocationCustomizer>> MACHINE_LOCATION_CUSTOMIZERS = ConfigKeys.newConfigKey(
+            new TypeToken<Collection<MachineLocationCustomizer>>() {},
+            "machineCustomizers", "Optional machine customizers");
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bac43b07/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 b0edf8c..78a61a9 100644
--- a/core/src/test/java/brooklyn/location/basic/FixedListMachineProvisioningLocationTest.java
+++ b/core/src/test/java/brooklyn/location/basic/FixedListMachineProvisioningLocationTest.java
@@ -34,10 +34,18 @@ import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
 import brooklyn.entity.basic.Entities;
 import brooklyn.location.LocationSpec;
 import brooklyn.location.MachineLocation;
 import brooklyn.location.NoMachinesAvailableException;
+import brooklyn.location.basic.RecordingMachineLocationCustomizer.Call;
 import brooklyn.management.internal.LocalManagementContext;
 import brooklyn.test.entity.LocalManagementContextForTests;
 import brooklyn.util.collections.MutableList;
@@ -45,13 +53,6 @@ import brooklyn.util.collections.MutableMap;
 import brooklyn.util.net.Networking;
 import brooklyn.util.stream.Streams;
 
-import com.google.common.base.Function;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-
 /**
  * Provisions {@link SshMachineLocation}s in a specific location from a list of known machines
  */
@@ -472,6 +473,35 @@ public class FixedListMachineProvisioningLocationTest {
         }
     }
 
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testMachineCustomizerSetOnByon() throws Exception {
+        machine = mgmt.getLocationManager().createLocation(MutableMap.of("address", Inet4Address.getByName("192.168.144.200")), SshMachineLocation.class);
+        RecordingMachineLocationCustomizer customizer = new RecordingMachineLocationCustomizer();
+        
+        provisioner2 = mgmt.getLocationManager().createLocation(LocationSpec.create(FixedListMachineProvisioningLocation.class)
+                .configure("machines", ImmutableList.of(machine))
+                .configure(FixedListMachineProvisioningLocation.MACHINE_LOCATION_CUSTOMIZERS.getName(), ImmutableList.of(customizer)));
+                
+        SshMachineLocation obtained = provisioner2.obtain();
+        assertEquals(Iterables.getOnlyElement(customizer.calls), new Call("customize", ImmutableList.of(obtained)));
+        
+        provisioner2.release(obtained);
+        assertEquals(customizer.calls.size(), 2);
+        assertEquals(Iterables.get(customizer.calls, 1), new Call("preRelease", ImmutableList.of(obtained)));
+    }
+
+    @Test
+    public void testMachineCustomizerSetOnObtainCall() throws Exception {
+        RecordingMachineLocationCustomizer customizer = new RecordingMachineLocationCustomizer();
+        
+        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);
+    }
+
     private static <T> List<T> randomized(Iterable<T> list) {
         // TODO inefficient implementation, but don't care for small tests
         Random random = new Random();

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bac43b07/core/src/test/java/brooklyn/location/basic/RecordingMachineLocationCustomizer.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/location/basic/RecordingMachineLocationCustomizer.java b/core/src/test/java/brooklyn/location/basic/RecordingMachineLocationCustomizer.java
new file mode 100644
index 0000000..0422b36
--- /dev/null
+++ b/core/src/test/java/brooklyn/location/basic/RecordingMachineLocationCustomizer.java
@@ -0,0 +1,71 @@
+/*
+ * 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.location.basic;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.List;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import brooklyn.location.MachineLocation;
+import brooklyn.location.MachineLocationCustomizer;
+
+public class RecordingMachineLocationCustomizer implements MachineLocationCustomizer {
+    public static class Call {
+        public final String methodName;
+        public final List<?> args;
+        
+        Call(String methodName, List<?> args) {
+            this.methodName = checkNotNull(methodName);
+            this.args = checkNotNull(args);
+        }
+        
+        @Override
+        public String toString() {
+            return methodName+args;
+        }
+        
+        @Override
+        public int hashCode() {
+            return Objects.hashCode(methodName, args);
+        }
+        
+        @Override
+        public boolean equals(Object other) {
+            return (other instanceof RecordingMachineLocationCustomizer.Call) && 
+                    methodName.equals(((RecordingMachineLocationCustomizer.Call)other).methodName) && 
+                    args.equals(((RecordingMachineLocationCustomizer.Call)other).args);
+        }
+    }
+    
+    public final List<RecordingMachineLocationCustomizer.Call> calls = Lists.newCopyOnWriteArrayList();
+    
+    @Override
+    public void customize(MachineLocation machine) {
+        calls.add(new Call("customize", ImmutableList.of(machine)));
+    }
+
+    @Override
+    public void preRelease(MachineLocation machine) {
+        calls.add(new Call("preRelease", ImmutableList.of(machine)));
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bac43b07/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java b/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java
index 60119ea..c7d9f95 100644
--- a/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java
+++ b/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java
@@ -26,11 +26,6 @@ import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.jclouds.compute.options.RunScriptOptions.Builder.overrideLoginCredentials;
 import static org.jclouds.scriptbuilder.domain.Statements.exec;
 
-import brooklyn.util.flags.MethodCoercions;
-import brooklyn.location.basic.AbstractLocation;
-import io.cloudsoft.winrm4j.pywinrm.Session;
-import io.cloudsoft.winrm4j.pywinrm.WinRMFactory;
-
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
@@ -93,6 +88,31 @@ import org.jclouds.softlayer.compute.options.SoftLayerTemplateOptions;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Charsets;
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Objects;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.base.Splitter;
+import com.google.common.base.Stopwatch;
+import com.google.common.base.Supplier;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Sets.SetView;
+import com.google.common.io.Files;
+import com.google.common.net.HostAndPort;
+import com.google.common.primitives.Ints;
+
 import brooklyn.config.ConfigKey;
 import brooklyn.config.ConfigKey.HasConfigKey;
 import brooklyn.config.ConfigUtils;
@@ -103,11 +123,13 @@ import brooklyn.entity.rebind.persister.PersistenceObjectStore;
 import brooklyn.entity.rebind.persister.jclouds.JcloudsBlobStoreBasedObjectStore;
 import brooklyn.location.LocationSpec;
 import brooklyn.location.MachineLocation;
+import brooklyn.location.MachineLocationCustomizer;
 import brooklyn.location.MachineManagementMixins.MachineMetadata;
 import brooklyn.location.MachineManagementMixins.RichMachineProvisioningLocation;
 import brooklyn.location.NoMachinesAvailableException;
 import brooklyn.location.access.PortForwardManager;
 import brooklyn.location.access.PortMapping;
+import brooklyn.location.basic.AbstractLocation;
 import brooklyn.location.basic.BasicMachineMetadata;
 import brooklyn.location.basic.LocationConfigKeys;
 import brooklyn.location.basic.LocationConfigUtils;
@@ -132,6 +154,7 @@ import brooklyn.util.crypto.SecureKeys;
 import brooklyn.util.exceptions.CompoundRuntimeException;
 import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.exceptions.ReferenceWithError;
+import brooklyn.util.flags.MethodCoercions;
 import brooklyn.util.flags.SetFromFlag;
 import brooklyn.util.flags.TypeCoercions;
 import brooklyn.util.guava.Maybe;
@@ -156,31 +179,8 @@ import brooklyn.util.text.Strings;
 import brooklyn.util.text.TemplateProcessor;
 import brooklyn.util.time.Duration;
 import brooklyn.util.time.Time;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Charsets;
-import com.google.common.base.Function;
-import com.google.common.base.Joiner;
-import com.google.common.base.Objects;
-import com.google.common.base.Optional;
-import com.google.common.base.Predicate;
-import com.google.common.base.Predicates;
-import com.google.common.base.Splitter;
-import com.google.common.base.Stopwatch;
-import com.google.common.base.Supplier;
-import com.google.common.base.Throwables;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Iterators;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-import com.google.common.collect.Sets.SetView;
-import com.google.common.io.Files;
-import com.google.common.net.HostAndPort;
-import com.google.common.primitives.Ints;
+import io.cloudsoft.winrm4j.pywinrm.Session;
+import io.cloudsoft.winrm4j.pywinrm.WinRMFactory;
 
 /**
  * For provisioning and managing VMs in a particular provider/region, using jclouds.
@@ -440,6 +440,11 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
         return result;
     }
 
+    protected Collection<MachineLocationCustomizer> getMachineCustomizers(ConfigBag setup) {
+        Collection<MachineLocationCustomizer> customizers = setup.get(MACHINE_LOCATION_CUSTOMIZERS);
+        return (customizers == null ? ImmutableList.<MachineLocationCustomizer>of() : customizers);
+    }
+
     public void setDefaultImageId(String val) {
         config().set(DEFAULT_IMAGE_ID, val);
     }
@@ -958,6 +963,9 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
             for (JcloudsLocationCustomizer customizer : getCustomizers(setup)) {
                 customizer.customize(this, computeService, machineLocation);
             }
+            for (MachineLocationCustomizer customizer : getMachineCustomizers(setup)) {
+                customizer.customize(machineLocation);
+            }
 
             customizedTimestamp = Duration.of(provisioningStopwatch);
 
@@ -2167,6 +2175,9 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                 if (tothrow==null) tothrow = e;
             }
         }
+        for (MachineLocationCustomizer customizer : getMachineCustomizers(setup)) {
+            customizer.preRelease(machine);
+        }
 
         try {
             // FIXME: Needs to release port forwarding for WinRmMachineLocations

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bac43b07/locations/jclouds/src/test/java/brooklyn/location/jclouds/JcloudsLocationTest.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/test/java/brooklyn/location/jclouds/JcloudsLocationTest.java b/locations/jclouds/src/test/java/brooklyn/location/jclouds/JcloudsLocationTest.java
index c175e29..5676787 100644
--- a/locations/jclouds/src/test/java/brooklyn/location/jclouds/JcloudsLocationTest.java
+++ b/locations/jclouds/src/test/java/brooklyn/location/jclouds/JcloudsLocationTest.java
@@ -24,6 +24,7 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
+
 import javax.annotation.Nullable;
 
 import org.jclouds.scriptbuilder.domain.OsFamily;
@@ -48,6 +49,7 @@ import brooklyn.entity.basic.ConfigKeys;
 import brooklyn.entity.basic.Entities;
 import brooklyn.location.LocationSpec;
 import brooklyn.location.MachineLocation;
+import brooklyn.location.MachineLocationCustomizer;
 import brooklyn.location.NoMachinesAvailableException;
 import brooklyn.location.basic.LocationConfigKeys;
 import brooklyn.location.cloud.names.CustomMachineNamer;
@@ -383,6 +385,9 @@ public class JcloudsLocationTest implements JcloudsLocationConfig {
             for (JcloudsLocationCustomizer customizer : getCustomizers(config().getBag())) {
                 customizer.customize(this, null, (JcloudsMachineLocation)result);
             }
+            for (MachineLocationCustomizer customizer : getMachineCustomizers(config().getBag())) {
+                customizer.customize((JcloudsMachineLocation)result);
+            }
 
             return result;
         }
@@ -440,22 +445,27 @@ public class JcloudsLocationTest implements JcloudsLocationConfig {
     @Test
     public void testInvokesCustomizerCallbacks() throws Exception {
         JcloudsLocationCustomizer customizer = Mockito.mock(JcloudsLocationCustomizer.class);
+        MachineLocationCustomizer machineCustomizer = Mockito.mock(MachineLocationCustomizer.class);
 //        Mockito.when(customizer.customize(Mockito.any(JcloudsLocation.class), Mockito.any(ComputeService.class), Mockito.any(JcloudsSshMachineLocation.class)));
         ConfigBag allConfig = ConfigBag.newInstance()
             .configure(CLOUD_PROVIDER, "aws-ec2")
             .configure(ACCESS_IDENTITY, "bogus")
             .configure(ACCESS_CREDENTIAL, "bogus")
             .configure(JcloudsLocationConfig.JCLOUDS_LOCATION_CUSTOMIZERS, ImmutableList.of(customizer))
+            .configure(JcloudsLocation.MACHINE_LOCATION_CUSTOMIZERS, ImmutableList.of(machineCustomizer))
             .configure(JcloudsLocation.MACHINE_CREATE_ATTEMPTS, 1);
         FakeLocalhostWithParentJcloudsLocation ll = managementContext.getLocationManager().createLocation(LocationSpec.create(FakeLocalhostWithParentJcloudsLocation.class).configure(allConfig.getAllConfig()));
         JcloudsMachineLocation l = (JcloudsMachineLocation)ll.obtain();
         Mockito.verify(customizer, Mockito.times(1)).customize(ll, null, l);
         Mockito.verify(customizer, Mockito.never()).preRelease(l);
         Mockito.verify(customizer, Mockito.never()).postRelease(l);
+        Mockito.verify(machineCustomizer, Mockito.times(1)).customize(l);
+        Mockito.verify(machineCustomizer, Mockito.never()).preRelease(l);
         
         ll.release(l);
         Mockito.verify(customizer, Mockito.times(1)).preRelease(l);
         Mockito.verify(customizer, Mockito.times(1)).postRelease(l);
+        Mockito.verify(machineCustomizer, Mockito.times(1)).preRelease(l);
     }
 
     // now test creating users