You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2015/05/22 11:04:57 UTC

[12/23] incubator-brooklyn git commit: improve the clouds machine namer

improve the clouds machine namer

make it an interface, move to sub-package, give good javadoc, and make the methods a little bit more intuitive.

fixes problems that CustomMachineNamer wasn't actually being invoked anymore since we called in via groups!
all the salting (unique id) stuff is a little confused, because different jclouds providers seem to do it in different ways.


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

Branch: refs/heads/master
Commit: 8008df4f2da54bc13bf373c6179cb889e165eec7
Parents: fa09efc
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Fri May 8 14:01:49 2015 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri May 8 18:51:49 2015 +0100

----------------------------------------------------------------------
 .../location/cloud/CloudLocationConfig.java     |   6 +-
 .../location/cloud/CloudMachineNamer.java       | 169 -------------------
 .../location/cloud/CustomMachineNamer.java      |  72 --------
 .../cloud/names/AbstractCloudMachineNamer.java  | 149 ++++++++++++++++
 .../cloud/names/BasicCloudMachineNamer.java     |  91 ++++++++++
 .../location/cloud/names/CloudMachineNamer.java |  61 +++++++
 .../cloud/names/CustomMachineNamer.java         |  72 ++++++++
 .../location/cloud/CloudMachineNamerTest.java   |  45 ++---
 .../location/cloud/CustomMachineNamerTest.java  |   7 +-
 .../location/jclouds/JcloudsLocation.java       |  10 +-
 .../location/jclouds/JcloudsLocationConfig.java |   1 +
 .../location/jclouds/JcloudsMachineNamer.java   |  11 +-
 .../location/jclouds/JcloudsLocationTest.java   |   8 +-
 .../jclouds/JcloudsMachineNamerTest.java        |  10 +-
 .../entity/service/InitdServiceInstaller.java   |   4 +-
 15 files changed, 430 insertions(+), 286 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8008df4f/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 5aad7bd..39ef6fa 100644
--- a/core/src/main/java/brooklyn/location/cloud/CloudLocationConfig.java
+++ b/core/src/main/java/brooklyn/location/cloud/CloudLocationConfig.java
@@ -59,7 +59,11 @@ public interface CloudLocationConfig {
 
     // default is just shy of common 64-char boundary (could perhaps increase slightly...)
     public static final ConfigKey<Integer> VM_NAME_MAX_LENGTH = ConfigKeys.newIntegerConfigKey(
-            "vmNameMaxLength", "Maximum length of VM name", 61);
+        "vmNameMaxLength", "Maximum length of VM name", 61);
+
+    public static final ConfigKey<Integer> VM_NAME_SALT_LENGTH = ConfigKeys.newIntegerConfigKey(
+        "vmNameSaltLength", "Number of characters to use for a random identifier inserted in hostname "
+            + "to uniquely identify machines", 4);
 
     public static final ConfigKey<String> WAIT_FOR_SSHABLE = ConfigKeys.newStringConfigKey("waitForSshable", 
             "Whether and how long to wait for a newly provisioned VM to be accessible via ssh; " +

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8008df4f/core/src/main/java/brooklyn/location/cloud/CloudMachineNamer.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/location/cloud/CloudMachineNamer.java b/core/src/main/java/brooklyn/location/cloud/CloudMachineNamer.java
deleted file mode 100644
index 5200a1c..0000000
--- a/core/src/main/java/brooklyn/location/cloud/CloudMachineNamer.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * 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.cloud;
-
-import brooklyn.entity.Application;
-import brooklyn.entity.Entity;
-import brooklyn.entity.trait.HasShortName;
-import brooklyn.util.config.ConfigBag;
-import brooklyn.util.text.Identifiers;
-import brooklyn.util.text.StringShortener;
-import brooklyn.util.text.Strings;
-
-import com.google.common.base.CharMatcher;
-
-public class CloudMachineNamer {
-
-    protected final ConfigBag setup;
-    int defaultMachineNameMaxLength = CloudLocationConfig.VM_NAME_MAX_LENGTH.getDefaultValue();
-    int nameInGroupReservedLength = 9;
-
-    public CloudMachineNamer(ConfigBag setup) {
-        this.setup = setup;
-    }
-    
-    public String generateNewMachineUniqueName() {
-        return generateNewIdReservingLength(0);
-    }
-    
-    public String generateNewMachineUniqueNameFromGroupId(String groupId) {
-        int suffixLength = getMaxNameLength() - (groupId.length() + 1); // +1 for the hyphen
-        if (suffixLength <= 0) {
-            return groupId;
-        }
-            
-        return groupId + "-" + Identifiers.makeRandomId(Math.min(4, suffixLength));
-    }
-
-    public String generateNewGroupId() {
-        return generateNewIdReservingLength(nameInGroupReservedLength);
-    }
-    
-    protected String generateNewIdReservingLength(int lengthToReserve) {
-        Object context = setup.peek(CloudLocationConfig.CALLER_CONTEXT);
-        Entity entity = null;
-        if (context instanceof Entity) entity = (Entity) context;
-        
-        StringShortener shortener = Strings.shortener().separator("-");
-        shortener.append("system", "brooklyn");
-        
-        // randId often not necessary, as an 8-char hex identifier is added later (in jclouds? can we override?)
-        // however it can be useful to have this early in the string, to prevent collisions in places where it is abbreviated 
-        shortener.append("randId", Identifiers.makeRandomId(4));
-        
-        String user = System.getProperty("user.name");
-        if (!"brooklyn".equals(user))
-            // include user; unless the user is 'brooklyn', as 'brooklyn-brooklyn-' is just silly!
-            shortener.append("user", user);
-        
-        if (entity!=null) {
-            Application app = entity.getApplication();
-            if (app!=null) {
-                shortener.append("app", shortName(app))
-                        .append("appId", app.getId());
-            }
-            shortener.append("entity", shortName(entity))
-                    .append("entityId", entity.getId());
-        } else if (context!=null) {
-            shortener.append("context", context.toString());
-        }
-        
-        shortener.truncate("user", 12)
-                .truncate("app", 16)
-                .truncate("entity", 16)
-                .truncate("appId", 4)
-                .truncate("entityId", 4)
-                .truncate("context", 12);
-        
-        shortener.canTruncate("user", 8)
-                .canTruncate("app", 5)
-                .canTruncate("entity", 5)
-                .canTruncate("system", 2)
-                .canTruncate("app", 3)
-                .canTruncate("entity", 3)
-                .canRemove("app")
-                .canTruncate("user", 4)
-                .canRemove("entity")
-                .canTruncate("context", 4)
-                .canTruncate("randId", 2)
-                .canRemove("user")
-                .canTruncate("appId", 2)
-                .canRemove("appId");
-        
-        int len = getMaxNameLength();
-        // decrement by e.g. 9 chars because jclouds adds that (dash plus 8 for hex id)
-        len -= lengthToReserve;
-        if (len<=0) return "";
-        String s = shortener.getStringOfMaxLength(len);
-        return sanitize(s).toLowerCase();
-    }
-
-    /** returns the max length of a VM name for the cloud specified in setup;
-     * this value is typically decremented by 9 to make room for jclouds labels;
-     * delegates to {@link #getCustomMaxNameLength()} when 
-     * {@link CloudLocationConfig#VM_NAME_MAX_LENGTH} is not set */
-    public int getMaxNameLength() {
-        if (setup.containsKey(CloudLocationConfig.VM_NAME_MAX_LENGTH)) {
-            // if a length is set, use that
-            return setup.get(CloudLocationConfig.VM_NAME_MAX_LENGTH);
-        }
-        
-        Integer custom = getCustomMaxNameLength();
-        if (custom!=null) return custom;
-        
-        // return the default
-        return defaultMachineNameMaxLength;  
-    }
-    
-    public CloudMachineNamer lengthMaxPermittedForMachineName(int defaultMaxLength) {
-        this.defaultMachineNameMaxLength = defaultMaxLength;
-        return this;
-    }
-
-    /** number of chars to use or reserve for the machine identifier when constructing a group identifier;
-     * defaults to 9, e.g. a hyphen and 8 random chars which is the jclouds model 
-     * @return */
-    public CloudMachineNamer lengthReservedForNameInGroup(int identifierRequiredLength) {
-        this.nameInGroupReservedLength = identifierRequiredLength;
-        return this;
-    }
-    
-    /** method for overriding to provide custom logic when an explicit config key is not set */
-    public Integer getCustomMaxNameLength() {
-        return null;
-    }
-
-    protected String shortName(Object x) {
-        if (x instanceof HasShortName) {
-            return ((HasShortName)x).getShortName();
-        }
-        if (x instanceof Entity) {
-            return ((Entity)x).getDisplayName();
-        }
-        return x.toString();
-    }
-
-    public static String sanitize(String s) {
-        return CharMatcher.inRange('A', 'Z')
-                .or(CharMatcher.inRange('a', 'z'))
-                .or(CharMatcher.inRange('0', '9'))
-                .negate()
-                .trimAndCollapseFrom(s, '-');
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8008df4f/core/src/main/java/brooklyn/location/cloud/CustomMachineNamer.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/location/cloud/CustomMachineNamer.java b/core/src/main/java/brooklyn/location/cloud/CustomMachineNamer.java
deleted file mode 100644
index c818dc9..0000000
--- a/core/src/main/java/brooklyn/location/cloud/CustomMachineNamer.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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.cloud;
-
-import java.util.Map;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.Entity;
-import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.entity.basic.EntityInternal;
-import brooklyn.util.config.ConfigBag;
-import brooklyn.util.text.Strings;
-import brooklyn.util.text.TemplateProcessor;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.reflect.TypeToken;
-
-public class CustomMachineNamer extends CloudMachineNamer {
-    
-    public static final ConfigKey<String> MACHINE_NAME_TEMPLATE = ConfigKeys.newStringConfigKey("custom.machine.namer.machine", 
-            "Freemarker template format for custom machine name", "${entity.displayName}");
-    @SuppressWarnings("serial")
-    public static final ConfigKey<Map<String, ?>> EXTRA_SUBSTITUTIONS = ConfigKeys.newConfigKey(new TypeToken<Map<String, ?>>() {}, 
-            "custom.machine.namer.substitutions", "Additional substitutions to be used in the template", ImmutableMap.<String, Object>of());
-    
-    public CustomMachineNamer(ConfigBag setup) {
-        super(setup);
-    }
-    
-    @Override
-    public String generateNewMachineUniqueName() {
-        Object context = setup.peek(CloudLocationConfig.CALLER_CONTEXT);
-        Entity entity = null;
-        if (context instanceof Entity) {
-            entity = (Entity) context;
-        }
-        
-        String template = this.setup.get(MACHINE_NAME_TEMPLATE);
-        
-        String processed;
-        if (entity == null) {
-            processed = TemplateProcessor.processTemplateContents(template, this.setup.get(EXTRA_SUBSTITUTIONS));
-        } else {
-            processed = TemplateProcessor.processTemplateContents(template, (EntityInternal)entity, this.setup.get(EXTRA_SUBSTITUTIONS));
-        }
-        
-        processed = Strings.removeFromStart(processed, "#ftl\n");
-        
-        return sanitize(processed);
-    }
-    
-    @Override
-    public String generateNewMachineUniqueNameFromGroupId(String groupId) {
-        return generateNewMachineUniqueName();
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8008df4f/core/src/main/java/brooklyn/location/cloud/names/AbstractCloudMachineNamer.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/location/cloud/names/AbstractCloudMachineNamer.java b/core/src/main/java/brooklyn/location/cloud/names/AbstractCloudMachineNamer.java
new file mode 100644
index 0000000..4e81a46
--- /dev/null
+++ b/core/src/main/java/brooklyn/location/cloud/names/AbstractCloudMachineNamer.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package brooklyn.location.cloud.names;
+
+import brooklyn.entity.Entity;
+import brooklyn.entity.trait.HasShortName;
+import brooklyn.location.cloud.CloudLocationConfig;
+import brooklyn.util.config.ConfigBag;
+import brooklyn.util.text.Identifiers;
+import brooklyn.util.text.Strings;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.CharMatcher;
+
+/** 
+ * Implements <b>most</b> of {@link CloudMachineNamer},
+ * leaving just one method -- {@link #generateNewIdOfLength(int)} --
+ * for subclasses to provide.
+ * <p>
+ * {@link CloudLocationConfig#VM_NAME_MAX_LENGTH} is used to find the VM length, 
+ * unless {@link #getCustomMaxNameLength(ConfigBag)} is overridden or
+ * {@link #setDefaultMachineNameMaxLength(int)} invoked on the instance supplied.
+ */
+public abstract class AbstractCloudMachineNamer implements CloudMachineNamer {
+
+    int defaultMachineNameMaxLength = CloudLocationConfig.VM_NAME_MAX_LENGTH.getDefaultValue();
+    int defaultMachineNameSaltLength = CloudLocationConfig.VM_NAME_SALT_LENGTH.getDefaultValue();
+    protected String separator = "-";
+
+    public String generateNewMachineUniqueName(ConfigBag setup) {
+        return generateNewIdReservingLength(setup, 0);
+    }
+    
+    public String generateNewMachineUniqueNameFromGroupId(ConfigBag setup, String groupId) {
+        int availSaltLength = getMaxNameLength(setup) - (groupId.length() + separator.length());
+        int requestedSaltLength = getLengthForMachineUniqueNameSalt(setup, false);
+        if (availSaltLength <= 0 || requestedSaltLength <= 0) {
+            return groupId;
+        }
+            
+        return sanitize(groupId + separator + Identifiers.makeRandomId(Math.min(requestedSaltLength, availSaltLength))).toLowerCase();
+    }
+
+    public String generateNewGroupId(ConfigBag setup) {
+        return sanitize(generateNewIdReservingLength(setup, getLengthForMachineUniqueNameSalt(setup, true))).toLowerCase();
+    }
+
+    protected String generateNewIdReservingLength(ConfigBag setup, int lengthToReserve) {
+        int len = getMaxNameLength(setup);
+        // decrement by e.g. 9 chars because jclouds adds that (dash plus 8 for hex id)
+        len -= lengthToReserve;
+        if (len<=0) return "";
+        return Strings.maxlen(generateNewIdOfLength(setup, len), len);
+    }
+    
+    /** Method for subclasses to provide to construct the context-specific part of an identifier,
+     * for use in {@link #generateNewGroupId()} and {@link #generateNewMachineUniqueName()}.
+     * 
+     * @param maxLengthHint an indication of the maximum length permitted for the ID generated,
+     * supplied for implementations which wish to use this information to decide what to truncate.
+     * (This class will truncate any return values longer than this.) 
+     */
+    protected abstract String generateNewIdOfLength(ConfigBag setup, int maxLengthHint);
+
+    /** Returns the max length of a VM name for the cloud specified in setup;
+     * this value is typically decremented by 9 to make room for jclouds labels;
+     * delegates to {@link #getCustomMaxNameLength()} when 
+     * {@link CloudLocationConfig#VM_NAME_MAX_LENGTH} is not set */
+    public int getMaxNameLength(ConfigBag setup) {
+        if (setup.containsKey(CloudLocationConfig.VM_NAME_MAX_LENGTH)) {
+            // if a length is set explicitly, use that (but intercept default behaviour)
+            return setup.get(CloudLocationConfig.VM_NAME_MAX_LENGTH);
+        }
+        
+        Integer custom = getCustomMaxNameLength(setup);
+        if (custom!=null) return custom;
+        
+        // return the default
+        return defaultMachineNameMaxLength;  
+    }
+    
+    public int getLengthForMachineUniqueNameSalt(ConfigBag setup, boolean includeSeparator) {
+        int saltLen;
+        if (setup.containsKey(CloudLocationConfig.VM_NAME_SALT_LENGTH)) {
+            saltLen = setup.get(CloudLocationConfig.VM_NAME_SALT_LENGTH);
+        } else {
+            // default value comes from key, but custom default can be set
+            saltLen = defaultMachineNameSaltLength;
+        }
+        
+        if (saltLen>0 && includeSeparator)
+            saltLen += separator.length();
+        
+        return saltLen;
+    }
+    
+    public AbstractCloudMachineNamer setDefaultMachineNameMaxLength(int defaultMaxLength) {
+        this.defaultMachineNameMaxLength = defaultMaxLength;
+        return this;
+    }
+
+    /** Number of chars to use or reserve for the machine identifier when constructing a group identifier;
+     * jclouds for instance uses "-" plus 8 */
+    public AbstractCloudMachineNamer setDefaultMachineNameSeparatorAndSaltLength(String separator, int defaultMachineUniqueNameSaltLength) {
+        this.separator = separator;
+        this.defaultMachineNameSaltLength = defaultMachineUniqueNameSaltLength;
+        return this;
+    }
+    
+    /** Method for overriding to provide custom logic when an explicit config key is not set for the machine length. */
+    public Integer getCustomMaxNameLength(ConfigBag setup) {
+        return null;
+    }
+
+    protected static String shortName(Object x) {
+        if (x instanceof HasShortName) {
+            return ((HasShortName)x).getShortName();
+        }
+        if (x instanceof Entity) {
+            return ((Entity)x).getDisplayName();
+        }
+        return x.toString();
+    }
+
+    @Beta //probably won't live here long-term
+    public static String sanitize(String s) {
+        return CharMatcher.inRange('A', 'Z')
+                .or(CharMatcher.inRange('a', 'z'))
+                .or(CharMatcher.inRange('0', '9'))
+                .negate()
+                .trimAndCollapseFrom(s, '-');
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8008df4f/core/src/main/java/brooklyn/location/cloud/names/BasicCloudMachineNamer.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/location/cloud/names/BasicCloudMachineNamer.java b/core/src/main/java/brooklyn/location/cloud/names/BasicCloudMachineNamer.java
new file mode 100644
index 0000000..626a7ea
--- /dev/null
+++ b/core/src/main/java/brooklyn/location/cloud/names/BasicCloudMachineNamer.java
@@ -0,0 +1,91 @@
+/*
+ * 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.cloud.names;
+
+import brooklyn.entity.Application;
+import brooklyn.entity.Entity;
+import brooklyn.location.cloud.CloudLocationConfig;
+import brooklyn.util.config.ConfigBag;
+import brooklyn.util.text.Identifiers;
+import brooklyn.util.text.StringShortener;
+import brooklyn.util.text.Strings;
+
+/** 
+ * Standard implementation of {@link CloudMachineNamer},
+ * which looks at several of the properties of the context (entity)
+ * and is clever about abbreviating them. */
+public class BasicCloudMachineNamer extends AbstractCloudMachineNamer {
+
+    @Override
+    protected String generateNewIdOfLength(ConfigBag setup, int len) {
+        Object context = setup.peek(CloudLocationConfig.CALLER_CONTEXT);
+        Entity entity = null;
+        if (context instanceof Entity) entity = (Entity) context;
+        
+        StringShortener shortener = Strings.shortener().separator("-");
+        shortener.append("system", "brooklyn");
+        
+        // randId often not necessary, as an 8-char hex identifier is added later (in jclouds? can we override?)
+        // however it can be useful to have this early in the string, to prevent collisions in places where it is abbreviated 
+        shortener.append("randId", Identifiers.makeRandomId(4));
+        
+        String user = System.getProperty("user.name");
+        if (!"brooklyn".equals(user))
+            // include user; unless the user is 'brooklyn', as 'brooklyn-brooklyn-' is just silly!
+            shortener.append("user", user);
+        
+        if (entity!=null) {
+            Application app = entity.getApplication();
+            if (app!=null) {
+                shortener.append("app", shortName(app))
+                        .append("appId", app.getId());
+            }
+            shortener.append("entity", shortName(entity))
+                    .append("entityId", entity.getId());
+        } else if (context!=null) {
+            shortener.append("context", context.toString());
+        }
+        
+        shortener.truncate("user", 12)
+                .truncate("app", 16)
+                .truncate("entity", 16)
+                .truncate("appId", 4)
+                .truncate("entityId", 4)
+                .truncate("context", 12);
+        
+        shortener.canTruncate("user", 8)
+                .canTruncate("app", 5)
+                .canTruncate("entity", 5)
+                .canTruncate("system", 2)
+                .canTruncate("app", 3)
+                .canTruncate("entity", 3)
+                .canRemove("app")
+                .canTruncate("user", 4)
+                .canRemove("entity")
+                .canTruncate("context", 4)
+                .canTruncate("randId", 2)
+                .canRemove("user")
+                .canTruncate("appId", 2)
+                .canRemove("appId");
+        
+        String s = shortener.getStringOfMaxLength(len);
+        return sanitize(s).toLowerCase();
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8008df4f/core/src/main/java/brooklyn/location/cloud/names/CloudMachineNamer.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/location/cloud/names/CloudMachineNamer.java b/core/src/main/java/brooklyn/location/cloud/names/CloudMachineNamer.java
new file mode 100644
index 0000000..022c1f4
--- /dev/null
+++ b/core/src/main/java/brooklyn/location/cloud/names/CloudMachineNamer.java
@@ -0,0 +1,61 @@
+/*
+ * 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.cloud.names;
+
+import brooklyn.entity.Entity;
+import brooklyn.location.Location;
+import brooklyn.location.cloud.CloudLocationConfig;
+import brooklyn.util.config.ConfigBag;
+
+/**
+ * Interface used to construct names for individual cloud machines and for groups of machines.
+ * <p>
+ * Implementations <b>must</b> provide a constructor which takes a single argument,
+ * being the {@link ConfigBag} for the context where the machine is being created
+ * (usually a {@link Location}).
+ * <p>
+ * With that bag, the config key {@link CloudLocationConfig#CALLER_CONTEXT}
+ * typically contains the {@link Entity} for which the machine is being created.   
+ */
+public interface CloudMachineNamer {
+
+    /**
+     * Generate a name for a new machine, based on context.
+     * <p>
+     * The name should normally be unique, as a context might produce multiple machines,
+     * for example basing it partially on information from the context but also including some random salt.
+     */
+    public String generateNewMachineUniqueName(ConfigBag setup);
+    /**
+     * Generate a name stem for a group of machines, based on context.
+     * <p>
+     * The name does not need to be unique, as uniqueness will be applied by {@link #generateNewMachineUniqueNameFromGroupId(String)}.
+     */
+    public String generateNewGroupId(ConfigBag setup);
+    
+    /**
+     * Generate a unique name from the given name stem.
+     * <p>
+     * The name stem is normally based on context information so the usual
+     * function of this method is to apply a suffix which helps to uniquely distinguish between machines
+     * in cases where the same name stem ({@link #generateNewGroupId()}) is used for multiple machines.
+     */
+    public String generateNewMachineUniqueNameFromGroupId(ConfigBag setup, String groupId);
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8008df4f/core/src/main/java/brooklyn/location/cloud/names/CustomMachineNamer.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/location/cloud/names/CustomMachineNamer.java b/core/src/main/java/brooklyn/location/cloud/names/CustomMachineNamer.java
new file mode 100644
index 0000000..9fde17e
--- /dev/null
+++ b/core/src/main/java/brooklyn/location/cloud/names/CustomMachineNamer.java
@@ -0,0 +1,72 @@
+/*
+ * 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.cloud.names;
+
+import java.util.Map;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.EntityInternal;
+import brooklyn.location.cloud.CloudLocationConfig;
+import brooklyn.util.config.ConfigBag;
+import brooklyn.util.text.Strings;
+import brooklyn.util.text.TemplateProcessor;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.reflect.TypeToken;
+
+/** Provides a machine namer which looks at a location config key {@link #MACHINE_NAME_TEMPLATE}
+ * to construct the hostname.
+ * For instance, setting this to <code>${config.entity_hostname}</code>
+ * will take the hostname from an <code>entity_hostname</code> key passed as entity <code>brooklyn.config</code>.
+ * <p>
+ * Note that this is not jclouds aware, so jclouds-specific cloud max lengths are not observed with this class.
+ */
+public class CustomMachineNamer extends BasicCloudMachineNamer {
+    
+    public static final ConfigKey<String> MACHINE_NAME_TEMPLATE = ConfigKeys.newStringConfigKey("custom.machine.namer.machine", 
+            "Freemarker template format for custom machine name", "${entity.displayName}");
+    @SuppressWarnings("serial")
+    public static final ConfigKey<Map<String, ?>> EXTRA_SUBSTITUTIONS = ConfigKeys.newConfigKey(new TypeToken<Map<String, ?>>() {}, 
+            "custom.machine.namer.substitutions", "Additional substitutions to be used in the template", ImmutableMap.<String, Object>of());
+    
+    @Override
+    protected String generateNewIdOfLength(ConfigBag setup, int len) {
+        Object context = setup.peek(CloudLocationConfig.CALLER_CONTEXT);
+        Entity entity = null;
+        if (context instanceof Entity) {
+            entity = (Entity) context;
+        }
+        
+        String template = setup.get(MACHINE_NAME_TEMPLATE);
+        
+        String processed;
+        if (entity == null) {
+            processed = TemplateProcessor.processTemplateContents(template, setup.get(EXTRA_SUBSTITUTIONS));
+        } else {
+            processed = TemplateProcessor.processTemplateContents(template, (EntityInternal)entity, setup.get(EXTRA_SUBSTITUTIONS));
+        }
+        
+        processed = Strings.removeFromStart(processed, "#ftl\n");
+        
+        return sanitize(processed);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8008df4f/core/src/test/java/brooklyn/location/cloud/CloudMachineNamerTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/location/cloud/CloudMachineNamerTest.java b/core/src/test/java/brooklyn/location/cloud/CloudMachineNamerTest.java
index 4da08bc..0bff9d0 100644
--- a/core/src/test/java/brooklyn/location/cloud/CloudMachineNamerTest.java
+++ b/core/src/test/java/brooklyn/location/cloud/CloudMachineNamerTest.java
@@ -30,6 +30,9 @@ import org.testng.annotations.Test;
 import brooklyn.entity.basic.ApplicationBuilder;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.location.cloud.names.AbstractCloudMachineNamer;
+import brooklyn.location.cloud.names.BasicCloudMachineNamer;
+import brooklyn.location.cloud.names.CloudMachineNamer;
 import brooklyn.test.entity.LocalManagementContextForTests;
 import brooklyn.test.entity.TestApplication;
 import brooklyn.test.entity.TestEntity;
@@ -54,7 +57,7 @@ public class CloudMachineNamerTest {
         ConfigBag cfg = new ConfigBag()
             .configure(CloudLocationConfig.CALLER_CONTEXT, child);
 
-        String result = new CloudMachineNamer(cfg).generateNewGroupId();
+        String result = new BasicCloudMachineNamer().generateNewGroupId(cfg);
 
         log.info("test entity child group id gives: "+result);
         // e.g. brooklyn-alex-tistapp-uube-testent-xisg-rwad
@@ -75,10 +78,10 @@ public class CloudMachineNamerTest {
 
         ConfigBag cfg = new ConfigBag()
             .configure(CloudLocationConfig.CALLER_CONTEXT, child);
-        CloudMachineNamer namer = new CloudMachineNamer(cfg);
+        BasicCloudMachineNamer namer = new BasicCloudMachineNamer();
         
-        String result = namer.generateNewMachineUniqueName();
-        Assert.assertTrue(result.length() <= namer.getMaxNameLength());
+        String result = namer.generateNewMachineUniqueName(cfg);
+        Assert.assertTrue(result.length() <= namer.getMaxNameLength(cfg));
         String user = Strings.maxlen(System.getProperty("user.name"), 4).toLowerCase();
         Assert.assertTrue(result.indexOf(user) >= 0);
         Assert.assertTrue(result.indexOf("-tistapp-") >= 0);
@@ -94,10 +97,10 @@ public class CloudMachineNamerTest {
 
         ConfigBag cfg = new ConfigBag()
             .configure(CloudLocationConfig.CALLER_CONTEXT, child);
-        CloudMachineNamer namer = new CloudMachineNamer(cfg);
+        CloudMachineNamer namer = new BasicCloudMachineNamer();
         
-        String groupId = namer.generateNewGroupId();
-        String result = namer.generateNewMachineUniqueNameFromGroupId(groupId);
+        String groupId = namer.generateNewGroupId(cfg);
+        String result = namer.generateNewMachineUniqueNameFromGroupId(cfg, groupId);
         Assert.assertTrue(result.startsWith(groupId));
         Assert.assertTrue(result.length() == groupId.length() + 5);
     }
@@ -109,24 +112,28 @@ public class CloudMachineNamerTest {
         
         ConfigBag cfg = new ConfigBag()
             .configure(CloudLocationConfig.CALLER_CONTEXT, child);
-        CloudMachineNamer namer = new CloudMachineNamer(cfg);
-        namer.lengthMaxPermittedForMachineName(10);
-        String result = namer.generateNewMachineUniqueName();
+        BasicCloudMachineNamer namer = new BasicCloudMachineNamer();
+        namer.setDefaultMachineNameMaxLength(10);
+        String result = namer.generateNewMachineUniqueName(cfg);
         Assert.assertEquals(result.length(), 10);
     }
     
     @Test
-    public void testLengthReserverdForNameInGroup() {
+    public void testLengthReservedForNameInGroup() {
         app = ApplicationBuilder.newManagedApp(EntitySpec.create(TestApplication.class).displayName("TistApp"), LocalManagementContextForTests.newInstance());
         TestEntity child = app.createAndManageChild(EntitySpec.create(TestEntity.class).displayName("TestEnt"));
         
         ConfigBag cfg = new ConfigBag()
             .configure(CloudLocationConfig.CALLER_CONTEXT, child);
-        CloudMachineNamer namer = new CloudMachineNamer(cfg);
-        namer.lengthMaxPermittedForMachineName(10);
-        namer.lengthReservedForNameInGroup(4);
-        String groupId = namer.generateNewGroupId();
-        Assert.assertEquals(5, groupId.length(), "groupId="+groupId);
+        BasicCloudMachineNamer namer = new BasicCloudMachineNamer();
+        namer.setDefaultMachineNameMaxLength(20);
+        namer.setDefaultMachineNameSeparatorAndSaltLength(":I", 5);
+        String groupId = namer.generateNewGroupId(cfg);
+        Assert.assertEquals(13, groupId.length(), "groupId="+groupId);
+        String machineId = namer.generateNewMachineUniqueNameFromGroupId(cfg, groupId);
+        Assert.assertEquals(20, machineId.length(), "machineId="+machineId);
+        // separator is not sanitized -- caller should know what they are doing there!
+        Assert.assertTrue(machineId.startsWith(groupId+"-i"), "machineId="+machineId);
     }
 
     @Test
@@ -136,9 +143,9 @@ public class CloudMachineNamerTest {
 
         ConfigBag cfg = new ConfigBag()
             .configure(CloudLocationConfig.CALLER_CONTEXT, child);
-        CloudMachineNamer namer = new CloudMachineNamer(cfg);
+        CloudMachineNamer namer = new BasicCloudMachineNamer();
         
-        String result = namer.generateNewMachineUniqueName();
+        String result = namer.generateNewMachineUniqueName(cfg);
         assertTrue(result.indexOf("t-ap") >= 0, "result="+result);
         for (int c : "_%$()\r\n\t[]*.!".getBytes()) {
             assertFalse(result.contains(new String(new char [] {(char)c})), "result="+result);
@@ -147,7 +154,7 @@ public class CloudMachineNamerTest {
     
     @Test
     public void testSanitize() {
-        Assert.assertEquals(CloudMachineNamer.sanitize(
+        Assert.assertEquals(AbstractCloudMachineNamer.sanitize(
                 "me & you like alphanumeric but not _ or !!! or dots...dots...dots %$()\r\n\t[]*etc"),
                 "me-you-like-alphanumeric-but-not-or-or-dots-dots-dots-etc");
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8008df4f/core/src/test/java/brooklyn/location/cloud/CustomMachineNamerTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/location/cloud/CustomMachineNamerTest.java b/core/src/test/java/brooklyn/location/cloud/CustomMachineNamerTest.java
index 829edb4..322acd1 100644
--- a/core/src/test/java/brooklyn/location/cloud/CustomMachineNamerTest.java
+++ b/core/src/test/java/brooklyn/location/cloud/CustomMachineNamerTest.java
@@ -26,6 +26,7 @@ import org.testng.annotations.Test;
 import brooklyn.entity.basic.ApplicationBuilder;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.location.cloud.names.CustomMachineNamer;
 import brooklyn.test.entity.LocalManagementContextForTests;
 import brooklyn.test.entity.TestApplication;
 import brooklyn.test.entity.TestEntity;
@@ -55,21 +56,21 @@ public class CustomMachineNamerTest {
     @Test
     public void testMachineNameNoConfig() {
         config.configure(CloudLocationConfig.CALLER_CONTEXT, child);
-        Assert.assertEquals(new CustomMachineNamer(config).generateNewMachineUniqueName(), "TestEnt");
+        Assert.assertEquals(new CustomMachineNamer().generateNewMachineUniqueName(config), "TestEnt");
     }
     
     @Test
     public void testMachineNameWithConfig() {
         child.setSequenceValue(999);
         config.configure(CustomMachineNamer.MACHINE_NAME_TEMPLATE, "number${entity.sequenceValue}");
-        Assert.assertEquals(new CustomMachineNamer(config).generateNewMachineUniqueName(), "number999");
+        Assert.assertEquals(new CustomMachineNamer().generateNewMachineUniqueName(config), "number999");
     }
     
     @Test
     public void testMachineNameWithExtraSubstitutions() {
         config.configure(CustomMachineNamer.MACHINE_NAME_TEMPLATE, "foo-${fooName}-bar-${barName}-baz-${bazName.substitution}")
             .configure(CustomMachineNamer.EXTRA_SUBSTITUTIONS, ImmutableMap.of("fooName", "foo", "barName", "bar", "bazName", this));
-        Assert.assertEquals(new CustomMachineNamer(config).generateNewMachineUniqueName(), "foo-foo-bar-bar-baz-baz");
+        Assert.assertEquals(new CustomMachineNamer().generateNewMachineUniqueName(config), "foo-foo-bar-bar-baz-baz");
     }
     
     public String getSubstitution() {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8008df4f/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 45019d5..1e1dccf 100644
--- a/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java
+++ b/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java
@@ -108,7 +108,7 @@ import brooklyn.location.basic.LocationConfigUtils.OsCredential;
 import brooklyn.location.basic.SshMachineLocation;
 import brooklyn.location.cloud.AbstractCloudMachineProvisioningLocation;
 import brooklyn.location.cloud.AvailabilityZoneExtension;
-import brooklyn.location.cloud.CloudMachineNamer;
+import brooklyn.location.cloud.names.CloudMachineNamer;
 import brooklyn.location.jclouds.JcloudsPredicates.NodeInLocation;
 import brooklyn.location.jclouds.networking.JcloudsPortForwarderExtension;
 import brooklyn.location.jclouds.templates.PortableTemplateBuilder;
@@ -321,14 +321,14 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
     protected CloudMachineNamer getCloudMachineNamer(ConfigBag config) {
         String namerClass = config.get(LocationConfigKeys.CLOUD_MACHINE_NAMER_CLASS);
         if (Strings.isNonBlank(namerClass)) {
-            Optional<CloudMachineNamer> cloudNamer = Reflections.invokeConstructorWithArgs(getManagementContext().getCatalogClassLoader(), namerClass, config);
+            Optional<CloudMachineNamer> cloudNamer = Reflections.invokeConstructorWithArgs(getManagementContext().getCatalogClassLoader(), namerClass);
             if (cloudNamer.isPresent()) {
                 return cloudNamer.get();
             } else {
                 throw new IllegalStateException("Failed to create CloudMachineNamer "+namerClass+" for location "+this);
             }
         } else {
-            return new JcloudsMachineNamer(config);
+            return new JcloudsMachineNamer();
         }
     }
 
@@ -565,7 +565,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
 
         final ComputeService computeService = getConfig(COMPUTE_SERVICE_REGISTRY).findComputeService(setup, true);
         CloudMachineNamer cloudMachineNamer = getCloudMachineNamer(setup);
-        String groupId = elvis(setup.get(GROUP_ID), cloudMachineNamer.generateNewGroupId());
+        String groupId = elvis(setup.get(GROUP_ID), cloudMachineNamer.generateNewGroupId(setup));
         NodeMetadata node = null;
         JcloudsSshMachineLocation sshMachineLocation = null;
 
@@ -609,7 +609,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
                             setup.getUnusedConfig());
 
                 templateTimestamp = Duration.of(provisioningStopwatch);
-                template.getOptions().getUserMetadata().put("Name", cloudMachineNamer.generateNewMachineUniqueNameFromGroupId(groupId));
+                template.getOptions().getUserMetadata().put("Name", cloudMachineNamer.generateNewMachineUniqueNameFromGroupId(setup, groupId));
 
                 nodes = computeService.createNodesInGroup(groupId, 1, template);
                 provisionTimestamp = Duration.of(provisioningStopwatch);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8008df4f/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocationConfig.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocationConfig.java b/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocationConfig.java
index b7b61a7..3e03837 100644
--- a/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocationConfig.java
+++ b/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocationConfig.java
@@ -250,6 +250,7 @@ public interface JcloudsLocationConfig extends CloudLocationConfig {
             "Registry/Factory for creating jclouds ComputeService; default is almost always fine, except where tests want to customize behaviour",
             ComputeServiceRegistryImpl.INSTANCE);
     
+    @SuppressWarnings("serial")
     public static final ConfigKey<Map<String,String>> TEMPLATE_OPTIONS = ConfigKeys.newConfigKey(
             new TypeToken<Map<String, String>>() {}, "templateOptions", "Additional jclouds template options");
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8008df4f/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsMachineNamer.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsMachineNamer.java b/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsMachineNamer.java
index c3f7b28..301ed43 100644
--- a/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsMachineNamer.java
+++ b/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsMachineNamer.java
@@ -18,18 +18,15 @@
  */
 package brooklyn.location.jclouds;
 
-import brooklyn.location.cloud.CloudMachineNamer;
+import brooklyn.location.cloud.names.BasicCloudMachineNamer;
 import brooklyn.util.config.ConfigBag;
 
-public class JcloudsMachineNamer extends CloudMachineNamer {
-
-    public JcloudsMachineNamer(ConfigBag setup) {
-        super(setup);
-    }
+public class JcloudsMachineNamer extends BasicCloudMachineNamer {
 
+    @Override
     /** returns the max length of a VM name for the cloud specified in setup;
      * this value is typically decremented by 9 to make room for jclouds labels */
-    public Integer getCustomMaxNameLength() {
+    public Integer getCustomMaxNameLength(ConfigBag setup) {
         // otherwise, for some known clouds which only allow a short name, use that length
         if ("vcloud".equals( setup.peek(JcloudsLocationConfig.CLOUD_PROVIDER) ))
             return 24;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8008df4f/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 60aec22..b133457 100644
--- a/locations/jclouds/src/test/java/brooklyn/location/jclouds/JcloudsLocationTest.java
+++ b/locations/jclouds/src/test/java/brooklyn/location/jclouds/JcloudsLocationTest.java
@@ -47,6 +47,7 @@ import brooklyn.entity.basic.Entities;
 import brooklyn.location.LocationSpec;
 import brooklyn.location.NoMachinesAvailableException;
 import brooklyn.location.basic.LocationConfigKeys;
+import brooklyn.location.cloud.names.CustomMachineNamer;
 import brooklyn.location.geo.HostGeoInfo;
 import brooklyn.location.jclouds.JcloudsLocation.UserCreation;
 import brooklyn.management.internal.LocalManagementContext;
@@ -436,10 +437,10 @@ public class JcloudsLocationTest implements JcloudsLocationConfig {
     
     @Test
     public void testCreateWithCustomMachineNamer() {
-        final String machineNamerClass = "brooklyn.location.cloud.CustomMachineNamer";
+        final String machineNamerClass = CustomMachineNamer.class.getName();
         BailOutJcloudsLocation jcloudsLocation = newSampleBailOutJcloudsLocationForTesting(ImmutableMap.of(
                 LocationConfigKeys.CLOUD_MACHINE_NAMER_CLASS, machineNamerClass));
-        jcloudsLocation.tryObtainAndCheck(ImmutableMap.of(), new Predicate<ConfigBag>() {
+        jcloudsLocation.tryObtainAndCheck(ImmutableMap.of(CustomMachineNamer.MACHINE_NAME_TEMPLATE, "ignored"), new Predicate<ConfigBag>() {
             public boolean apply(ConfigBag input) {
                 Assert.assertEquals(input.get(LocationConfigKeys.CLOUD_MACHINE_NAMER_CLASS), machineNamerClass);
                 return true;
@@ -449,9 +450,10 @@ public class JcloudsLocationTest implements JcloudsLocationConfig {
     
     @Test
     public void testCreateWithCustomMachineNamerOnObtain() {
-        final String machineNamerClass = "brooklyn.location.cloud.CustomMachineNamer";
+        final String machineNamerClass = CustomMachineNamer.class.getName();
         BailOutJcloudsLocation jcloudsLocation = newSampleBailOutJcloudsLocationForTesting();
         jcloudsLocation.tryObtainAndCheck(ImmutableMap.of(
+                CustomMachineNamer.MACHINE_NAME_TEMPLATE, "ignored",
                 LocationConfigKeys.CLOUD_MACHINE_NAMER_CLASS, machineNamerClass), new Predicate<ConfigBag>() {
             public boolean apply(ConfigBag input) {
                 Assert.assertEquals(input.get(LocationConfigKeys.CLOUD_MACHINE_NAMER_CLASS), machineNamerClass);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8008df4f/locations/jclouds/src/test/java/brooklyn/location/jclouds/JcloudsMachineNamerTest.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/test/java/brooklyn/location/jclouds/JcloudsMachineNamerTest.java b/locations/jclouds/src/test/java/brooklyn/location/jclouds/JcloudsMachineNamerTest.java
index 2a51617..da21750 100644
--- a/locations/jclouds/src/test/java/brooklyn/location/jclouds/JcloudsMachineNamerTest.java
+++ b/locations/jclouds/src/test/java/brooklyn/location/jclouds/JcloudsMachineNamerTest.java
@@ -36,15 +36,15 @@ public class JcloudsMachineNamerTest {
             .configure(JcloudsLocationConfig.CLOUD_PROVIDER, "vcloud")
             .configure(JcloudsLocationConfig.CALLER_CONTEXT, "!mycontext!");
         
-        String result = new JcloudsMachineNamer(cfg).generateNewGroupId();
+        String result = new JcloudsMachineNamer().generateNewGroupId(cfg);
         
         log.info("test mycontext vcloud group id gives: "+result);
-        // brooklyn-user-mycontext!-1234
-        // br-user-myco-1234
-        Assert.assertTrue(result.length() <= 15);
+        // brooklyn-user-!mycontext!-1234
+        // br-<code>-<user>-myco-1234
+        Assert.assertTrue(result.length() <= 24-4-1, "result: "+result);
         
         String user = Strings.maxlen(System.getProperty("user.name"), 2).toLowerCase();
-        // (length 2 will happen if user is brooklyn)
+        // (length 2 will happen if user is brooklyn, to avoid brooklyn-brooklyn at start!)
         Assert.assertTrue(result.indexOf(user) >= 0);
         Assert.assertTrue(result.indexOf("-myc") >= 0);
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8008df4f/software/base/src/main/java/brooklyn/entity/service/InitdServiceInstaller.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/service/InitdServiceInstaller.java b/software/base/src/main/java/brooklyn/entity/service/InitdServiceInstaller.java
index ce52038..6586bb1 100644
--- a/software/base/src/main/java/brooklyn/entity/service/InitdServiceInstaller.java
+++ b/software/base/src/main/java/brooklyn/entity/service/InitdServiceInstaller.java
@@ -32,7 +32,7 @@ import brooklyn.entity.basic.SoftwareProcess;
 import brooklyn.entity.effector.EffectorTasks;
 import brooklyn.entity.trait.HasShortName;
 import brooklyn.location.basic.SshMachineLocation;
-import brooklyn.location.cloud.CloudMachineNamer;
+import brooklyn.location.cloud.names.AbstractCloudMachineNamer;
 import brooklyn.management.Task;
 import brooklyn.policy.Enricher;
 import brooklyn.util.ResourceUtils;
@@ -112,7 +112,7 @@ public class InitdServiceInstaller implements SystemServiceInstaller {
         } else {
             name = "brooklyn-service";
         }
-        return CloudMachineNamer.sanitize(name.toString()).toLowerCase();
+        return AbstractCloudMachineNamer.sanitize(name.toString()).toLowerCase();
     }
 
     private String getStartScriptName() {