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/08/19 13:10:16 UTC

[58/72] [abbrv] incubator-brooklyn git commit: BROOKLYN-162 - jclouds last few package prefixes needed, and tidy in core and elsewhere related (or observed in the process)

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/byon/FixedListMachineProvisioningLocation.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/byon/FixedListMachineProvisioningLocation.java b/core/src/main/java/org/apache/brooklyn/location/byon/FixedListMachineProvisioningLocation.java
new file mode 100644
index 0000000..14e43e5
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/location/byon/FixedListMachineProvisioningLocation.java
@@ -0,0 +1,476 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.location.byon;
+
+import static org.apache.brooklyn.util.GroovyJavaMethods.truth;
+
+import java.io.Closeable;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.location.MachineLocation;
+import org.apache.brooklyn.api.location.MachineLocationCustomizer;
+import org.apache.brooklyn.api.location.MachineProvisioningLocation;
+import org.apache.brooklyn.api.location.NoMachinesAvailableException;
+import org.apache.brooklyn.api.mgmt.LocationManager;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+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 org.apache.brooklyn.location.cloud.CloudLocationConfig;
+import org.apache.brooklyn.location.core.AbstractLocation;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.util.collections.CollectionFunctionals;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.collections.MutableSet;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.flags.SetFromFlag;
+import org.apache.brooklyn.util.stream.Streams;
+import org.apache.brooklyn.util.text.WildcardGlobs;
+import org.apache.brooklyn.util.text.WildcardGlobs.PhraseTreatment;
+
+/**
+ * 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,
+ * for example a list of machines which can be SSH'd to. 
+ * 
+ * This can be extended to have a mechanism to make more machines to be available
+ * (override provisionMore and canProvisionMore).
+ */
+public class FixedListMachineProvisioningLocation<T extends MachineLocation> extends AbstractLocation 
+implements MachineProvisioningLocation<T>, Closeable {
+
+    // TODO Synchronization looks very wrong for accessing machines/inUse 
+    // e.g. removeChild doesn't synchronize when doing machines.remove(...),
+    // and getMachines() returns the real sets risking 
+    // ConcurrentModificationException in the caller if it iterates over them etc.
+    
+    private static final Logger log = LoggerFactory.getLogger(FixedListMachineProvisioningLocation.class);
+    
+    public static final ConfigKey<Function<Iterable<? extends MachineLocation>, MachineLocation>> MACHINE_CHOOSER =
+            ConfigKeys.newConfigKey(
+                    new TypeToken<Function<Iterable<? extends MachineLocation>, MachineLocation>>() {}, 
+                    "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();
+    
+    @SetFromFlag
+    protected Set<T> machines;
+    
+    @SetFromFlag
+    protected Set<T> inUse;
+
+    @SetFromFlag
+    protected Set<T> pendingRemoval;
+    
+    @SetFromFlag
+    protected Map<T, Map<String, Object>> origConfigs;
+
+    public FixedListMachineProvisioningLocation() {
+        this(Maps.newLinkedHashMap());
+    }
+    public FixedListMachineProvisioningLocation(Map properties) {
+        super(properties);
+
+        if (isLegacyConstruction()) {
+            init();
+        }
+    }
+
+    @Override
+    public void init() {
+        super.init();
+        
+        Set<T> machinesCopy = MutableSet.of();
+        for (T location: machines) {
+            if (location==null) {
+                log.warn(""+this+" initialized with null location, removing (may be due to rebind with reference to an unmanaged location)");
+            } else {
+                Location parent = location.getParent();
+                if (parent == null) {
+                    addChild(location);
+                }
+                machinesCopy.add(location);
+            }
+        }
+        if (!machinesCopy.equals(machines)) {
+            machines = machinesCopy;
+        }
+    }
+    
+    @Override
+    public String toVerboseString() {
+        return Objects.toStringHelper(this).omitNullValues()
+                .add("id", getId()).add("name", getDisplayName())
+                .add("machinesAvailable", getAvailable()).add("machinesInUse", getInUse())
+                .toString();
+    }
+
+    @Override
+    public AbstractLocation configure(Map<?,?> properties) {
+        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);
+    }
+    
+    @SuppressWarnings("unchecked")
+    public FixedListMachineProvisioningLocation<T> newSubLocation(Map<?,?> newFlags) {
+        // TODO shouldn't have to copy config bag as it should be inherited (but currently it is not used inherited everywhere; just most places)
+        return getManagementContext().getLocationManager().createLocation(LocationSpec.create(getClass())
+                .parent(this)
+                .configure(config().getLocalBag().getAllConfig())  // FIXME Should this just be inherited?
+                .configure(newFlags));
+    }
+
+    @Override
+    public void close() {
+        for (T machine : machines) {
+            if (machine instanceof Closeable) Streams.closeQuietly((Closeable)machine);
+        }
+    }
+    
+    public void addMachine(T machine) {
+        synchronized (lock) {
+            if (machines.contains(machine)) {
+                throw new IllegalArgumentException("Cannot add "+machine+" to "+toString()+", because already contained");
+            }
+            
+            Location existingParent = ((Location)machine).getParent();
+            if (existingParent == null) {
+                addChild(machine);
+            }
+            
+            machines.add(machine);
+        }
+    }
+    
+    public void removeMachine(T machine) {
+        synchronized (lock) {
+            if (inUse.contains(machine)) {
+                pendingRemoval.add(machine);
+            } else {
+                machines.remove(machine);
+                pendingRemoval.remove(machine);
+                if (this.equals(machine.getParent())) {
+                    removeChild((Location)machine);
+                }
+            }
+        }
+    }
+    
+    protected Set<T> getMachines() {
+        return machines;
+    }
+    
+    public Set<T> getAvailable() {
+        Set<T> a = Sets.newLinkedHashSet(machines);
+        a.removeAll(inUse);
+        return a;
+    }   
+     
+    public Set<T> getInUse() {
+        return Sets.newLinkedHashSet(inUse);
+    }   
+     
+    public Set<T> getAllMachines() {
+        return ImmutableSet.copyOf(machines);
+    }   
+     
+    @Override
+    public void addChild(Location child) {
+        super.addChild(child);
+        machines.add((T)child);
+    }
+
+    @Override
+    public boolean removeChild(Location child) {
+        if (inUse.contains(child)) {
+            throw new IllegalStateException("Child location "+child+" is in use; cannot remove from "+this);
+        }
+        machines.remove(child);
+        return super.removeChild(child);
+    }
+
+    protected boolean canProvisionMore() {
+        return false;
+    }
+    
+    protected void provisionMore(int size) {
+        provisionMore(size, ImmutableMap.of());
+    }
+
+    protected void provisionMore(int size, Map<?,?> flags) {
+        throw new IllegalStateException("more not permitted");
+    }
+
+    public T obtain() throws NoMachinesAvailableException {
+        return obtain(Maps.<String,Object>newLinkedHashMap());
+    }
+    
+    @Override
+    public T obtain(Map<?,?> flags) throws NoMachinesAvailableException {
+        T machine;
+        T desiredMachine = (T) flags.get("desiredMachine");
+        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, allflags.getAllConfig());
+                    a = getAvailable();
+                }
+                if (a.isEmpty())
+                    throw new NoMachinesAvailableException("No machines available in "+toString());
+            }
+            if (desiredMachine != null) {
+                if (a.contains(desiredMachine)) {
+                    machine = desiredMachine;
+                } else {
+                    throw new IllegalStateException("Desired machine "+desiredMachine+" not available in "+toString()+"; "+
+                            (inUse.contains(desiredMachine) ? "machine in use" : "machine unknown"));
+                }
+            } else {
+                machine = (T) chooser.apply(a);
+                if (!a.contains(machine)) {
+                    throw new IllegalStateException("Machine chooser attempted to choose '"+machine+"' from outside the available set, in "+this);
+                }
+            }
+            inUse.add(machine);
+            updateMachineConfig(machine, flags);
+        }
+        
+        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");
+            restoreMachineConfig(machine);
+            inUse.remove(machine);
+            
+            if (pendingRemoval.contains(machine)) {
+                removeMachine(machine);
+            }
+        }
+    }
+
+    @Override
+    public Map<String,Object> getProvisioningFlags(Collection<String> tags) {
+        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);
+        if (result == null) result = (K) overrides.get(key.getName());
+        if (result == null) result = getConfig(key);
+        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>
+     * {@code
+     *   new FixedListMachineProvisioningLocation.Builder()
+     *           .user("alex")
+     *           .keyFile("/Users/alex/.ssh/id_rsa")
+     *           .addAddress("10.0.0.1")
+     *           .addAddress("10.0.0.2")
+     *           .addAddress("10.0.0.3")
+     *           .addAddressMultipleTimes("me@127.0.0.1", 5)
+     *           .build();
+     * }
+     * </pre>
+     */
+    public static class Builder {
+        LocationManager lm;
+        String user;
+        String privateKeyPassphrase;
+        String privateKeyFile;
+        String privateKeyData;
+        File localTempDir;
+        List machines = Lists.newArrayList();
+
+        public Builder(LocationManager lm) {
+            this.lm = lm;
+        }
+        public Builder user(String user) {
+            this.user = user;
+            return this;
+        }
+        public Builder keyPassphrase(String keyPassphrase) {
+            this.privateKeyPassphrase = keyPassphrase;
+            return this; 
+        }
+        public Builder keyFile(String keyFile) {
+            this.privateKeyFile = keyFile;
+            return this; 
+        }
+        public Builder keyData(String keyData) {
+            this.privateKeyData = keyData;
+            return this;
+        }
+        public Builder localTempDir(File val) {
+            this.localTempDir = val;
+            return this;
+        }
+        /** adds the locations; user and keyfile set in the builder are _not_ applied to the machine
+         * (use add(String address) for that)
+         */
+        public Builder add(SshMachineLocation location) {
+            machines.add(location);
+            return this;
+        }
+        public Builder addAddress(String address) {
+            return addAddresses(address);
+        }
+        public Builder addAddressMultipleTimes(String address, int n) {
+            for (int i=0; i<n; i++)
+                addAddresses(address);
+            return this;
+        }
+        public Builder addAddresses(String address1, String ...others) {
+            List<String> addrs = new ArrayList<String>();
+            addrs.addAll(WildcardGlobs.getGlobsAfterBraceExpansion("{"+address1+"}",
+                    true /* numeric */, /* no quote support though */ PhraseTreatment.NOT_A_SPECIAL_CHAR, PhraseTreatment.NOT_A_SPECIAL_CHAR));
+            for (String address: others) 
+                addrs.addAll(WildcardGlobs.getGlobsAfterBraceExpansion("{"+address+"}",
+                        true /* numeric */, /* no quote support though */ PhraseTreatment.NOT_A_SPECIAL_CHAR, PhraseTreatment.NOT_A_SPECIAL_CHAR));
+            for (String addr: addrs)
+                add(createMachine(addr)); 
+            return this;
+        }
+        protected SshMachineLocation createMachine(String addr) {
+            if (lm==null)
+                return new SshMachineLocation(makeConfig(addr));
+            else
+                return lm.createLocation(makeConfig(addr), SshMachineLocation.class);
+        }
+        private Map makeConfig(String address) {
+            String user = this.user;
+            if (address.contains("@")) {
+                user = address.substring(0, address.indexOf("@"));
+                address = address.substring(address.indexOf("@")+1);
+            }
+            Map config = MutableMap.of("address", address);
+            if (truth(user)) {
+                config.put("user", user);
+                config.put("sshconfig.user", user);
+            }
+            if (truth(privateKeyPassphrase)) config.put("sshconfig.privateKeyPassphrase", privateKeyPassphrase);
+            if (truth(privateKeyFile)) config.put("sshconfig.privateKeyFile", privateKeyFile);
+            if (truth(privateKeyData)) config.put("sshconfig.privateKey", privateKeyData);
+            if (truth(localTempDir)) config.put("localTempDir", localTempDir);
+            return config;
+        }
+        @SuppressWarnings("unchecked")
+        public FixedListMachineProvisioningLocation<SshMachineLocation> build() {
+            if (lm==null)
+                return new FixedListMachineProvisioningLocation<SshMachineLocation>(MutableMap.builder()
+                    .putIfNotNull("machines", machines)
+                    .putIfNotNull("user", user)
+                    .putIfNotNull("privateKeyPassphrase", privateKeyPassphrase)
+                    .putIfNotNull("privateKeyFile", privateKeyFile)
+                    .putIfNotNull("privateKeyData", privateKeyData)
+                    .putIfNotNull("localTempDir", localTempDir)
+                    .build());
+            else
+                return lm.createLocation(MutableMap.builder()
+                    .putIfNotNull("machines", machines)
+                    .putIfNotNull("user", user)
+                    .putIfNotNull("privateKeyPassphrase", privateKeyPassphrase)
+                    .putIfNotNull("privateKeyFile", privateKeyFile)
+                    .putIfNotNull("privateKeyData", privateKeyData)
+                    .putIfNotNull("localTempDir", localTempDir)
+                    .build(), 
+                FixedListMachineProvisioningLocation.class);
+        }        
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/byon/HostLocationResolver.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/byon/HostLocationResolver.java b/core/src/main/java/org/apache/brooklyn/location/byon/HostLocationResolver.java
new file mode 100644
index 0000000..55263e2
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/location/byon/HostLocationResolver.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.location.byon;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationRegistry;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.location.core.AbstractLocationResolver;
+import org.apache.brooklyn.location.core.LocationConfigUtils;
+import org.apache.brooklyn.location.core.LocationPropertiesFromBrooklynProperties;
+import org.apache.brooklyn.location.core.internal.LocationInternal;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.guava.Maybe.Absent;
+import org.apache.brooklyn.util.text.KeyValueParser;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+
+public class HostLocationResolver extends AbstractLocationResolver {
+    
+    private static final String HOST = "host";
+    
+    @SuppressWarnings("rawtypes")
+    @Override
+    public Location newLocationFromString(Map locationFlags, String spec, LocationRegistry registry) {
+        // Extract args from spec
+        ParsedSpec parsedSpec = specParser.parse(spec);
+        Map<String, String> argsMap = parsedSpec.argsMap;
+        if (argsMap.isEmpty()) {
+            throw new IllegalArgumentException("Invalid host spec (no host supplied): "+spec);
+        } else if (argsMap.size() == 1 && Iterables.get(argsMap.values(), 0) == null) {
+            // only given ip or hostname
+            argsMap = ImmutableMap.of("hosts", Iterables.get(argsMap.keySet(), 0));
+        } else if (!(argsMap.containsKey("host") || argsMap.containsKey("hosts"))) {
+            throw new IllegalArgumentException("Invalid host spec (no host supplied): "+spec);
+        }
+
+        // Find generic applicable properties
+        Map globalProperties = registry.getProperties();
+        String namedLocation = (String) locationFlags.get(LocationInternal.NAMED_SPEC_NAME.getName());
+        Map<String, Object> filteredProperties = new LocationPropertiesFromBrooklynProperties().getLocationProperties(null, namedLocation, globalProperties);
+        ConfigBag flags = ConfigBag.newInstance(locationFlags).putIfAbsent(filteredProperties);
+        flags.remove(LocationInternal.NAMED_SPEC_NAME);
+
+        // Generate target spec
+        String target = "byon("+KeyValueParser.toLine(argsMap)+")";
+        Maybe<Location> testResolve = managementContext.getLocationRegistry().resolve(target, false, null);
+        if (!testResolve.isPresent()) {
+            throw new IllegalArgumentException("Invalid target location '" + target + "' for location '"+HOST+"': "+
+                Exceptions.collapseText( ((Absent<?>)testResolve).getException() ), ((Absent<?>)testResolve).getException());
+        }
+        
+        return managementContext.getLocationManager().createLocation(LocationSpec.create(SingleMachineProvisioningLocation.class)
+                .configure("location", target)
+                .configure("locationFlags", flags.getAllConfig())
+                .configure(LocationConfigUtils.finalAndOriginalSpecs(spec, locationFlags, globalProperties, namedLocation)));
+    }
+    
+    @Override
+    public String getPrefix() {
+        return HOST;
+    }
+    
+    @Override
+    protected Class<? extends Location> getLocationType() {
+        return SingleMachineProvisioningLocation.class;
+    }
+
+    @Override
+    protected SpecParser getSpecParser() {
+        return new SpecParser(getPrefix()).setExampleUsage("\"host(1.1.1.1)\" or \"host(host=1.1.1.1,name=myname)\"");
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/byon/SingleMachineLocationResolver.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/byon/SingleMachineLocationResolver.java b/core/src/main/java/org/apache/brooklyn/location/byon/SingleMachineLocationResolver.java
new file mode 100644
index 0000000..6265f5c
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/location/byon/SingleMachineLocationResolver.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.location.byon;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationRegistry;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.location.core.AbstractLocationResolver;
+import org.apache.brooklyn.location.core.LocationConfigUtils;
+import org.apache.brooklyn.location.core.LocationPropertiesFromBrooklynProperties;
+import org.apache.brooklyn.location.core.internal.LocationInternal;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.guava.Maybe.Absent;
+
+public class SingleMachineLocationResolver extends AbstractLocationResolver {
+    
+    private static final String SINGLE = "single";
+    
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Override
+    public Location newLocationFromString(Map locationFlags, String spec, LocationRegistry registry) {
+        ConfigBag config = extractConfig(locationFlags, spec, registry);
+        Map globalProperties = registry.getProperties();
+        String namedLocation = (String) locationFlags.get(LocationInternal.NAMED_SPEC_NAME.getName());
+        
+        if (registry != null) {
+            LocationPropertiesFromBrooklynProperties.setLocalTempDir(globalProperties, config);
+        }
+
+        if (config.getStringKey("target") == null) {
+            throw new IllegalArgumentException("target must be specified in single-machine spec");
+        }
+        String target = config.getStringKey("target").toString();
+        config.remove("target");
+        Maybe<Location> testResolve = managementContext.getLocationRegistry().resolve(target, false, null);
+        if (!testResolve.isPresent()) {
+            throw new IllegalArgumentException("Invalid target location '" + target + "' for location '"+SINGLE+"': "+
+                Exceptions.collapseText( ((Absent<?>)testResolve).getException() ));
+        }
+        
+        return managementContext.getLocationManager().createLocation(LocationSpec.create(SingleMachineProvisioningLocation.class)
+                .configure("location", target)
+                .configure("locationFlags", config.getAllConfig())
+                .configure(LocationConfigUtils.finalAndOriginalSpecs(spec, locationFlags, globalProperties, namedLocation)));
+    }
+    
+    @Override
+    public String getPrefix() {
+        return SINGLE;
+    }
+    
+    @Override
+    protected Class<? extends Location> getLocationType() {
+        return SingleMachineProvisioningLocation.class;
+    }
+
+    @Override
+    protected SpecParser getSpecParser() {
+        return new SpecParser(getPrefix()).setExampleUsage("\"single(target=jclouds:aws-ec2:us-east-1)\"");
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/byon/SingleMachineProvisioningLocation.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/byon/SingleMachineProvisioningLocation.java b/core/src/main/java/org/apache/brooklyn/location/byon/SingleMachineProvisioningLocation.java
new file mode 100644
index 0000000..3338da5
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/location/byon/SingleMachineProvisioningLocation.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 org.apache.brooklyn.location.byon;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.location.MachineLocation;
+import org.apache.brooklyn.api.location.MachineProvisioningLocation;
+import org.apache.brooklyn.api.location.NoMachinesAvailableException;
+import org.apache.brooklyn.util.core.flags.SetFromFlag;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableMap;
+
+public class SingleMachineProvisioningLocation<T extends MachineLocation> extends FixedListMachineProvisioningLocation<T> {
+    private static final long serialVersionUID = -4216528515792151062L;
+
+    private static final Logger log = LoggerFactory.getLogger(SingleMachineProvisioningLocation.class);
+    
+    @SetFromFlag(nullable=false)
+    private String location;
+    
+    @SetFromFlag(nullable=false)
+    private Map<?,?> locationFlags;
+    
+    private T singleLocation;
+    private int referenceCount;
+    private MachineProvisioningLocation<T> provisioningLocation;
+
+
+    public SingleMachineProvisioningLocation() {
+    }
+
+    @SuppressWarnings("rawtypes")
+    public SingleMachineProvisioningLocation(String location, Map locationFlags) {
+        this.locationFlags = locationFlags;
+        this.location = location;
+    }
+
+    @SuppressWarnings("rawtypes")
+    @Override
+    public synchronized T obtain(Map flags) throws NoMachinesAvailableException {
+        log.info("Flags {} passed to newLocationFromString will be ignored, using {}", flags, locationFlags);
+        return obtain();
+    }
+
+    @Override
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public synchronized T obtain() throws NoMachinesAvailableException {
+        if (singleLocation == null) {
+            if (provisioningLocation == null) {
+                provisioningLocation = (MachineProvisioningLocation) getManagementContext().getLocationRegistry().resolve(
+                    location, locationFlags);
+            }
+            singleLocation = provisioningLocation.obtain(ImmutableMap.of());
+            inUse.add(singleLocation);
+        }
+        referenceCount++;
+        return singleLocation;
+    }
+
+    @Override
+    public synchronized void release(T machine) {
+        if (!machine.equals(singleLocation)) {
+            throw new IllegalArgumentException("Invalid machine " + machine + " passed to release, expecting: " + singleLocation);
+        }
+        if (--referenceCount == 0) {
+            provisioningLocation.release(machine);
+            singleLocation = null;
+        }
+        inUse.remove(machine);
+    };
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/cloud/AbstractCloudMachineProvisioningLocation.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/cloud/AbstractCloudMachineProvisioningLocation.java b/core/src/main/java/org/apache/brooklyn/location/cloud/AbstractCloudMachineProvisioningLocation.java
index d09cadc..6a7dc73 100644
--- a/core/src/main/java/org/apache/brooklyn/location/cloud/AbstractCloudMachineProvisioningLocation.java
+++ b/core/src/main/java/org/apache/brooklyn/location/cloud/AbstractCloudMachineProvisioningLocation.java
@@ -24,7 +24,7 @@ import java.util.Map;
 import org.apache.brooklyn.api.location.LocationSpec;
 import org.apache.brooklyn.api.location.MachineLocation;
 import org.apache.brooklyn.api.location.MachineProvisioningLocation;
-import org.apache.brooklyn.location.basic.AbstractLocation;
+import org.apache.brooklyn.location.core.AbstractLocation;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.core.config.ConfigBag;
 import org.apache.brooklyn.util.core.internal.ssh.SshTool;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/cloud/AvailabilityZoneExtension.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/cloud/AvailabilityZoneExtension.java b/core/src/main/java/org/apache/brooklyn/location/cloud/AvailabilityZoneExtension.java
index 312cb83..c0143ae 100644
--- a/core/src/main/java/org/apache/brooklyn/location/cloud/AvailabilityZoneExtension.java
+++ b/core/src/main/java/org/apache/brooklyn/location/cloud/AvailabilityZoneExtension.java
@@ -22,7 +22,7 @@ import java.util.List;
 
 import org.apache.brooklyn.api.location.Location;
 import org.apache.brooklyn.entity.group.DynamicCluster;
-import org.apache.brooklyn.location.basic.MultiLocation;
+import org.apache.brooklyn.location.core.MultiLocation;
 
 import com.google.common.annotations.Beta;
 import com.google.common.base.Predicate;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/cloud/CloudLocationConfig.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/cloud/CloudLocationConfig.java b/core/src/main/java/org/apache/brooklyn/location/cloud/CloudLocationConfig.java
index 2bb2d7e..935a96c 100644
--- a/core/src/main/java/org/apache/brooklyn/location/cloud/CloudLocationConfig.java
+++ b/core/src/main/java/org/apache/brooklyn/location/cloud/CloudLocationConfig.java
@@ -27,7 +27,7 @@ import org.apache.brooklyn.api.location.MachineLocationCustomizer;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.BasicConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
-import org.apache.brooklyn.location.basic.LocationConfigKeys;
+import org.apache.brooklyn.location.core.LocationConfigKeys;
 import org.apache.brooklyn.util.core.flags.SetFromFlag;
 
 public interface CloudLocationConfig {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/core/AbstractLocation.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/core/AbstractLocation.java b/core/src/main/java/org/apache/brooklyn/location/core/AbstractLocation.java
new file mode 100644
index 0000000..026cf87
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/location/core/AbstractLocation.java
@@ -0,0 +1,709 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.location.core;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.apache.brooklyn.util.GroovyJavaMethods.elvis;
+import static org.apache.brooklyn.util.JavaGroovyEquivalents.groovyTruth;
+
+import java.io.Closeable;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.api.mgmt.rebind.RebindSupport;
+import org.apache.brooklyn.api.mgmt.rebind.mementos.LocationMemento;
+import org.apache.brooklyn.api.objs.Configurable;
+import org.apache.brooklyn.config.ConfigInheritance;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.config.ConfigKey.HasConfigKey;
+import org.apache.brooklyn.core.config.BasicConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.internal.BrooklynFeatureEnablement;
+import org.apache.brooklyn.core.internal.storage.BrooklynStorage;
+import org.apache.brooklyn.core.internal.storage.Reference;
+import org.apache.brooklyn.core.internal.storage.impl.BasicReference;
+import org.apache.brooklyn.core.mgmt.internal.LocalLocationManager;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.core.mgmt.rebind.BasicLocationRebindSupport;
+import org.apache.brooklyn.core.objs.AbstractBrooklynObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.brooklyn.location.core.internal.LocationDynamicType;
+import org.apache.brooklyn.location.core.internal.LocationInternal;
+import org.apache.brooklyn.location.geo.HasHostGeoInfo;
+import org.apache.brooklyn.location.geo.HostGeoInfo;
+import org.apache.brooklyn.util.collections.SetFromLiveMap;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.flags.FlagUtils;
+import org.apache.brooklyn.util.core.flags.TypeCoercions;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.stream.Streams;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Objects.ToStringHelper;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.reflect.TypeToken;
+
+/**
+ * A basic implementation of the {@link Location} interface.
+ *
+ * This provides an implementation which works according to the requirements of
+ * the interface documentation, and is ready to be extended to make more specialized locations.
+ * 
+ * Override {@link #configure(Map)} to add special initialization logic.
+ */
+public abstract class AbstractLocation extends AbstractBrooklynObject implements LocationInternal, HasHostGeoInfo, Configurable {
+    
+    private static final long serialVersionUID = -7495805474138619830L;
+
+    /** @deprecated since 0.7.0 shouldn't be public */
+    @Deprecated
+    public static final Logger LOG = LoggerFactory.getLogger(AbstractLocation.class);
+
+    public static final ConfigKey<Location> PARENT_LOCATION = new BasicConfigKey<Location>(Location.class, "parentLocation");
+
+    public static final ConfigKey<Boolean> TEMPORARY_LOCATION = ConfigKeys.newBooleanConfigKey("temporaryLocation",
+            "Indicates that the location is a temporary location that has been created to test connectivity, and that" +
+            "the location's events should not be recorded by usage listeners", false);
+
+    private final AtomicBoolean configured = new AtomicBoolean();
+    
+    private Reference<Long> creationTimeUtc = new BasicReference<Long>(System.currentTimeMillis());
+    
+    // _not_ set from flag; configured explicitly in configure, because we also need to update the parent's list of children
+    private Reference<Location> parent = new BasicReference<Location>();
+    
+    // NB: all accesses should be synchronized
+    private Set<Location> children = Sets.newLinkedHashSet();
+
+    private Reference<String> name = new BasicReference<String>();
+    private boolean displayNameAutoGenerated = true;
+
+    private Reference<HostGeoInfo> hostGeoInfo = new BasicReference<HostGeoInfo>();
+
+    private BasicConfigurationSupport config = new BasicConfigurationSupport();
+    
+    private ConfigBag configBag = new ConfigBag();
+
+    private volatile boolean managed;
+
+    private boolean inConstruction;
+
+    private Reference<Map<Class<?>, Object>> extensions = new BasicReference<Map<Class<?>, Object>>(Maps.<Class<?>, Object>newConcurrentMap());
+
+    private final LocationDynamicType locationType;
+
+    /**
+     * Construct a new instance of an AbstractLocation.
+     */
+    public AbstractLocation() {
+        this(Maps.newLinkedHashMap());
+    }
+    
+    /**
+     * Construct a new instance of an AbstractLocation.
+     *
+     * The properties map recognizes the following keys:
+     * <ul>
+     * <li>name - a name for the location
+     * <li>parentLocation - the parent {@link Location}
+     * </ul>
+     * 
+     * Other common properties (retrieved via get/findLocationProperty) include:
+     * <ul>
+     * <li>latitude
+     * <li>longitude
+     * <li>displayName
+     * <li>iso3166 - list of iso3166-2 code strings
+     * <li>timeZone
+     * <li>abbreviatedName
+     * </ul>
+     */
+    public AbstractLocation(Map<?,?> properties) {
+        super(properties);
+        inConstruction = true;
+        
+        // When one calls getConfig(key), we want to use the default value specified on *this* location
+        // if it overrides the default config, by using the type object 
+        locationType = new LocationDynamicType(this);
+        
+        if (isLegacyConstruction()) {
+            AbstractBrooklynObject checkWeGetThis = configure(properties);
+            assert this.equals(checkWeGetThis) : this+" configure method does not return itself; returns "+checkWeGetThis+" instead of "+this;
+
+            boolean deferConstructionChecks = (properties.containsKey("deferConstructionChecks") && TypeCoercions.coerce(properties.get("deferConstructionChecks"), Boolean.class));
+            if (!deferConstructionChecks) {
+                FlagUtils.checkRequiredFields(this);
+            }
+        }
+        
+        inConstruction = false;
+    }
+
+    protected void assertNotYetManaged() {
+        if (!inConstruction && Locations.isManaged(this)) {
+            LOG.warn("Configuration being made to {} after deployment; may not be supported in future versions", this);
+        }
+        //throw new IllegalStateException("Cannot set configuration "+key+" on active location "+this)
+    }
+
+    public void setManagementContext(ManagementContextInternal managementContext) {
+        super.setManagementContext(managementContext);
+        if (displayNameAutoGenerated && getId() != null) name.set(getClass().getSimpleName()+":"+getId().substring(0, Math.min(getId().length(),4)));
+
+        if (BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_USE_BROOKLYN_LIVE_OBJECTS_DATAGRID_STORAGE)) {
+            Location oldParent = parent.get();
+            Set<Location> oldChildren = children;
+            Map<String, Object> oldConfig = configBag.getAllConfig();
+            Long oldCreationTimeUtc = creationTimeUtc.get();
+            String oldDisplayName = name.get();
+            HostGeoInfo oldHostGeoInfo = hostGeoInfo.get();
+
+            parent = managementContext.getStorage().getReference(getId()+"-parent");
+            children = SetFromLiveMap.create(managementContext.getStorage().<Location,Boolean>getMap(getId()+"-children"));
+            creationTimeUtc = managementContext.getStorage().getReference(getId()+"-creationTime");
+            hostGeoInfo = managementContext.getStorage().getReference(getId()+"-hostGeoInfo");
+            name = managementContext.getStorage().getReference(getId()+"-displayName");
+
+            // Only override stored defaults if we have actual values. We might be in setManagementContext
+            // because we are reconstituting an existing entity in a new brooklyn management-node (in which
+            // case believe what is already in the storage), or we might be in the middle of creating a new 
+            // entity. Normally for a new entity (using EntitySpec creation approach), this will get called
+            // before setting the parent etc. However, for backwards compatibility we still support some
+            // things calling the entity's constructor directly.
+            if (oldParent != null) parent.set(oldParent);
+            if (oldChildren.size() > 0) children.addAll(oldChildren);
+            if (creationTimeUtc.isNull()) creationTimeUtc.set(oldCreationTimeUtc);
+            if (hostGeoInfo.isNull()) hostGeoInfo.set(oldHostGeoInfo);
+            if (name.isNull()) {
+                name.set(oldDisplayName);
+            } else {
+                displayNameAutoGenerated = false;
+            }
+
+            configBag = ConfigBag.newLiveInstance(managementContext.getStorage().<String,Object>getMap(getId()+"-config"));
+            if (oldConfig.size() > 0) {
+                configBag.putAll(oldConfig);
+            }
+        }
+    }
+
+    /**
+     * @deprecated since 0.7.0; only used for legacy brooklyn types where constructor is called directly;
+     * see overridden method for more info
+     */
+    @SuppressWarnings("serial")
+    @Override
+    @Deprecated
+    public AbstractLocation configure(Map<?,?> properties) {
+        assertNotYetManaged();
+        
+        boolean firstTime = !configured.getAndSet(true);
+            
+        configBag.putAll(properties);
+        
+        if (properties.containsKey(PARENT_LOCATION.getName())) {
+            // need to ensure parent's list of children is also updated
+            setParent(configBag.get(PARENT_LOCATION));
+            
+            // don't include parentLocation in configBag, as breaks rebind
+            configBag.remove(PARENT_LOCATION);
+        }
+
+        // NB: flag-setting done here must also be done in BasicLocationRebindSupport 
+        FlagUtils.setFieldsFromFlagsWithBag(this, properties, configBag, firstTime);
+        FlagUtils.setAllConfigKeys(this, configBag, false);
+
+        if (properties.containsKey("displayName")) {
+            name.set((String) removeIfPossible(properties, "displayName"));
+            displayNameAutoGenerated = false;
+        } else if (properties.containsKey("name")) {
+            name.set((String) removeIfPossible(properties, "name"));
+            displayNameAutoGenerated = false;
+        } else if (isLegacyConstruction()) {
+            name.set(getClass().getSimpleName()+":"+getId().substring(0, Math.min(getId().length(),4)));
+            displayNameAutoGenerated = true;
+        }
+
+        // TODO Explicitly dealing with iso3166 here because want custom splitter rule comma-separated string.
+        // Is there a better way to do it (e.g. more similar to latitude, where configKey+TypeCoercion is enough)?
+        if (groovyTruth(properties.get("iso3166"))) {
+            Object rawCodes = removeIfPossible(properties, "iso3166");
+            Set<String> codes;
+            if (rawCodes instanceof CharSequence) {
+                codes = ImmutableSet.copyOf(Splitter.on(",").trimResults().split((CharSequence)rawCodes));
+            } else {
+                codes = TypeCoercions.coerce(rawCodes, new TypeToken<Set<String>>() {});
+            }
+            configBag.put(LocationConfigKeys.ISO_3166, codes);
+        }
+        
+        return this;
+    }
+
+    // TODO ensure no callers rely on 'remove' semantics, and don't remove;
+    // or perhaps better use a config bag so we know what is used v unused
+    private static Object removeIfPossible(Map<?,?> map, Object key) {
+        try {
+            return map.remove(key);
+        } catch (Exception e) {
+            return map.get(key);
+        }
+    }
+    
+    public boolean isManaged() {
+        return getManagementContext() != null && managed;
+    }
+
+    public void onManagementStarted() {
+        if (displayNameAutoGenerated) name.set(getClass().getSimpleName()+":"+getId().substring(0, Math.min(getId().length(),4)));
+        this.managed = true;
+    }
+    
+    public void onManagementStopped() {
+        this.managed = false;
+        if (getManagementContext().isRunning()) {
+            BrooklynStorage storage = ((ManagementContextInternal)getManagementContext()).getStorage();
+            storage.remove(getId()+"-parent");
+            storage.remove(getId()+"-children");
+            storage.remove(getId()+"-creationTime");
+            storage.remove(getId()+"-hostGeoInfo");
+            storage.remove(getId()+"-displayName");
+            storage.remove(getId()+"-config");
+        }
+    }
+    
+    @Override
+    public String getDisplayName() {
+        return name.get();
+    }
+    
+    protected boolean isDisplayNameAutoGenerated() {
+        return displayNameAutoGenerated;
+    }
+    
+    @Override
+    public Location getParent() {
+        return parent.get();
+    }
+    
+    @Override
+    public Collection<Location> getChildren() {
+        synchronized (children) {
+            return ImmutableList.copyOf(children);
+        }
+    }
+
+    @Override
+    public void setParent(Location newParent) {
+        setParent(newParent, true);
+    }
+    
+    public void setParent(Location newParent, boolean updateChildListParents) {
+        if (newParent == this) {
+            throw new IllegalArgumentException("Location cannot be its own parent: "+this);
+        }
+        if (newParent == parent.get()) {
+            return; // no-op; already have desired parent
+        }
+        
+        if (parent.get() != null) {
+            Location oldParent = parent.get();
+            parent.set(null);
+            if (updateChildListParents)
+                ((AbstractLocation)oldParent).removeChild(this);
+        }
+        // TODO Should we support a location changing parent? The resulting unmanage/manage might cause problems.
+        // The code above suggests we do, but maybe we should warn or throw error, or at least test it!
+        
+        parent.set(newParent);
+        if (newParent != null) {
+            if (updateChildListParents)
+                ((AbstractLocation)newParent).addChild(this);
+        }
+        
+        onChanged();
+    }
+
+    @Override
+    public ConfigurationSupportInternal config() {
+        return config ;
+    }
+
+    private class BasicConfigurationSupport implements ConfigurationSupportInternal {
+
+        @Override
+        public <T> T get(ConfigKey<T> key) {
+            if (hasConfig(key, false)) return getLocalBag().get(key);
+            if (getParent() != null && isInherited(key)) {
+                return getParent().getConfig(key);
+            }
+            
+            // In case this entity class has overridden the given key (e.g. to set default), then retrieve this entity's key
+            // TODO when locations become entities, the duplication of this compared to EntityConfigMap.getConfig will disappear.
+            @SuppressWarnings("unchecked")
+            ConfigKey<T> ownKey = (ConfigKey<T>) elvis(locationType.getConfigKey(key.getName()), key);
+
+            return ownKey.getDefaultValue();
+        }
+
+        @Override
+        public <T> T get(HasConfigKey<T> key) {
+            return get(key.getConfigKey());
+        }
+
+        @Override
+        public <T> T set(ConfigKey<T> key, T val) {
+            T result = configBag.put(key, val);
+            onChanged();
+            return result;
+        }
+
+        @Override
+        public <T> T set(HasConfigKey<T> key, T val) {
+            return set(key.getConfigKey(), val);
+        }
+
+        @Override
+        public <T> T set(ConfigKey<T> key, Task<T> val) {
+            // TODO Support for locations
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public <T> T set(HasConfigKey<T> key, Task<T> val) {
+            // TODO Support for locations
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public ConfigBag getBag() {
+            ConfigBag result = ConfigBag.newInstanceExtending(configBag, ImmutableMap.of());
+            Location p = getParent();
+            if (p!=null) result.putIfAbsent(((LocationInternal)p).config().getBag());
+            return result;
+        }
+
+        @Override
+        public ConfigBag getLocalBag() {
+            return configBag;
+        }
+
+        @Override
+        public Maybe<Object> getRaw(ConfigKey<?> key) {
+            if (hasConfig(key, false)) return Maybe.of(getLocalBag().getStringKey(key.getName()));
+            if (getParent() != null && isInherited(key)) return ((LocationInternal)getParent()).config().getRaw(key);
+            return Maybe.absent();
+        }
+
+        @Override
+        public Maybe<Object> getRaw(HasConfigKey<?> key) {
+            return getRaw(key.getConfigKey());
+        }
+
+        @Override
+        public Maybe<Object> getLocalRaw(ConfigKey<?> key) {
+            if (hasConfig(key, false)) return Maybe.of(getLocalBag().getStringKey(key.getName()));
+            return Maybe.absent();
+        }
+
+        @Override
+        public Maybe<Object> getLocalRaw(HasConfigKey<?> key) {
+            return getLocalRaw(key.getConfigKey());
+        }
+
+        @Override
+        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
+        }
+        
+        @Override
+        public void refreshInheritedConfigOfChildren() {
+            // no-op for location
+        }
+        
+        private boolean hasConfig(ConfigKey<?> key, boolean includeInherited) {
+            if (includeInherited && isInherited(key)) {
+                return getBag().containsKey(key);
+            } else {
+                return getLocalBag().containsKey(key);
+            }
+        }
+        
+        private boolean isInherited(ConfigKey<?> key) {
+            ConfigInheritance inheritance = key.getInheritance();
+            if (inheritance==null) inheritance = getDefaultInheritance();
+            return inheritance.isInherited(key, getParent(), AbstractLocation.this);
+        }
+
+        private ConfigInheritance getDefaultInheritance() {
+            return ConfigInheritance.ALWAYS;
+        }
+    }
+    
+    @Override
+    public <T> T getConfig(HasConfigKey<T> key) {
+        return config().get(key);
+    }
+
+    @Override
+    public <T> T getConfig(ConfigKey<T> key) {
+        return config().get(key);
+    }
+
+    @Override
+    @Deprecated
+    public boolean hasConfig(ConfigKey<?> key, boolean includeInherited) {
+        return config.hasConfig(key, includeInherited);
+    }
+
+    @Override
+    @Deprecated
+    public Map<String,Object> getAllConfig(boolean includeInherited) {
+        // TODO Have no information about what to include/exclude inheritance wise.
+        // however few things use getAllConfigBag()
+        ConfigBag bag = (includeInherited ? config().getBag() : config().getLocalBag());
+        return bag.getAllConfig();
+    }
+    
+    @Override
+    @Deprecated
+    public ConfigBag getAllConfigBag() {
+        // TODO see comments in EntityConfigMap and on interface methods. 
+        // here ConfigBag is used exclusively so
+        // we have no information about what to include/exclude inheritance wise.
+        // however few things use getAllConfigBag()
+        return config().getBag();
+    }
+    
+    @Override
+    public ConfigBag getLocalConfigBag() {
+        return config().getLocalBag();
+    }
+
+    /** 
+     * @deprecated since 0.7; use {@link #getLocalConfigBag()}
+     * @since 0.6
+     */
+    @Deprecated
+    public ConfigBag getRawLocalConfigBag() {
+        return config().getLocalBag();
+    }
+    
+    @Override
+    @Deprecated
+    public <T> T setConfig(ConfigKey<T> key, T value) {
+        return config().set(key, value);
+    }
+
+    /**
+     * @since 0.6.0 (?) - use getDisplayName
+     * @deprecated since 0.7.0; use {@link #getDisplayName()}
+     */
+    @Deprecated
+    public void setName(String newName) {
+        setDisplayName(newName);
+    }
+
+    public void setDisplayName(String newName) {
+        name.set(newName);
+        displayNameAutoGenerated = false;
+        onChanged();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (! (o instanceof Location)) {
+            return false;
+        }
+
+        Location l = (Location) o;
+        return getId().equals(l.getId());
+    }
+
+    @Override
+    public int hashCode() {
+        return getId().hashCode();
+    }
+
+    @Override
+    public boolean containsLocation(Location potentialDescendent) {
+        Location loc = potentialDescendent;
+        while (loc != null) {
+            if (this == loc) return true;
+            loc = loc.getParent();
+        }
+        return false;
+    }
+
+    protected <T extends Location> T addChild(LocationSpec<T> spec) {
+        T child = getManagementContext().getLocationManager().createLocation(spec);
+        addChild(child);
+        return child;
+    }
+    
+    @SuppressWarnings("deprecation")
+    public void addChild(Location child) {
+        // Previously, setParent delegated to addChildLocation and we sometimes ended up with
+        // duplicate entries here. Instead this now uses a similar scheme to 
+        // AbstractLocation.setParent/addChild (with any weaknesses for distribution that such a 
+        // scheme might have...).
+        // 
+        // We continue to use a list to allow identical-looking locations, but they must be different 
+        // instances.
+        
+        synchronized (children) {
+            for (Location contender : children) {
+                if (contender == child) {
+                    // don't re-add; no-op
+                    return;
+                }
+            }
+
+            children.add(child);
+        }
+        
+        if (isManaged()) {
+            if (!getManagementContext().getLocationManager().isManaged(child)) {
+                Locations.manage(child, getManagementContext());
+            }
+        } else if (getManagementContext() != null) {
+            if (((LocalLocationManager)getManagementContext().getLocationManager()).getLocationEvenIfPreManaged(child.getId()) == null) {
+                ((ManagementContextInternal)getManagementContext()).prePreManage(child);
+            }
+        }
+
+        children.add(child);
+        child.setParent(this);
+        
+        onChanged();
+    }
+    
+    public boolean removeChild(Location child) {
+        boolean removed;
+        synchronized (children) {
+            removed = children.remove(child);
+        }
+        if (removed) {
+            if (child instanceof Closeable) {
+                Streams.closeQuietly((Closeable)child);
+            }
+            child.setParent(null);
+            
+            if (isManaged()) {
+                getManagementContext().getLocationManager().unmanage(child);
+            }
+        }
+        onChanged();
+        return removed;
+    }
+
+    protected void onChanged() {
+        // currently changes simply trigger re-persistence; there is no intermediate listener as we do for EntityChangeListener
+        if (isManaged()) {
+            getManagementContext().getRebindManager().getChangeListener().onChanged(this);
+        }
+    }
+
+    /** Default String representation is simplified name of class, together with selected fields. */
+    @Override
+    public String toString() {
+        return string().toString();
+    }
+    
+    @Override
+    public String toVerboseString() {
+        return toString();
+    }
+
+    /** override this, adding to the returned value, to supply additional fields to include in the toString */
+    protected ToStringHelper string() {
+        return Objects.toStringHelper(getClass()).add("id", getId()).add("name", name);
+    }
+    
+    @Override
+    public HostGeoInfo getHostGeoInfo() { return hostGeoInfo.get(); }
+    
+    public void setHostGeoInfo(HostGeoInfo hostGeoInfo) {
+        if (hostGeoInfo!=null) { 
+            this.hostGeoInfo.set(hostGeoInfo);
+            setConfig(LocationConfigKeys.LATITUDE, hostGeoInfo.latitude); 
+            setConfig(LocationConfigKeys.LONGITUDE, hostGeoInfo.longitude); 
+        } 
+    }
+
+    @Override
+    public RebindSupport<LocationMemento> getRebindSupport() {
+        return new BasicLocationRebindSupport(this);
+    }
+    
+    @Override
+    public boolean hasExtension(Class<?> extensionType) {
+        return extensions.get().containsKey(checkNotNull(extensionType, "extensionType"));
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T> T getExtension(Class<T> extensionType) {
+        Object extension = extensions.get().get(checkNotNull(extensionType, "extensionType"));
+        if (extension == null) {
+            throw new IllegalArgumentException("No extension of type "+extensionType+" registered for location "+this);
+        }
+        return (T) extension;
+    }
+    
+    @Override
+    public <T> void addExtension(Class<T> extensionType, T extension) {
+        checkNotNull(extensionType, "extensionType");
+        checkNotNull(extension, "extension");
+        checkArgument(extensionType.isInstance(extension), "extension %s does not implement %s", extension, extensionType);
+        extensions.get().put(extensionType, extension);
+    }
+
+    @Override
+    public Map<String, String> toMetadataRecord() {
+        ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
+        if (getDisplayName() != null) builder.put("displayName", getDisplayName());
+        if (getParent() != null && getParent().getDisplayName() != null) {
+            builder.put("parentDisplayName", getParent().getDisplayName());
+        }
+        return builder.build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/core/AbstractLocationResolver.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/core/AbstractLocationResolver.java b/core/src/main/java/org/apache/brooklyn/location/core/AbstractLocationResolver.java
new file mode 100644
index 0000000..c7e80bc
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/location/core/AbstractLocationResolver.java
@@ -0,0 +1,188 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.location.core;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationRegistry;
+import org.apache.brooklyn.api.location.LocationResolver;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.location.core.internal.LocationInternal;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.text.KeyValueParser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Examples of valid specs:
+ *   <ul>
+ *     <li>byon(hosts=myhost)
+ *     <li>byon(hosts=myhost,myhost2)
+ *     <li>byon(hosts="myhost, myhost2")
+ *     <li>byon(hosts=myhost,myhost2, name=abc)
+ *     <li>byon(hosts="myhost, myhost2", name="my location name")
+ *   </ul>
+ * 
+ * @author aled
+ */
+@SuppressWarnings({"unchecked","rawtypes"})
+public abstract class AbstractLocationResolver implements LocationResolver {
+
+    public static final Logger log = LoggerFactory.getLogger(AbstractLocationResolver.class);
+    
+    protected volatile ManagementContext managementContext;
+
+    protected volatile SpecParser specParser;
+
+    protected abstract Class<? extends Location> getLocationType();
+    
+    protected abstract SpecParser getSpecParser();
+    
+    @Override
+    public void init(ManagementContext managementContext) {
+        this.managementContext = checkNotNull(managementContext, "managementContext");
+        this.specParser = getSpecParser();
+    }
+    
+    @Override
+    public boolean accepts(String spec, LocationRegistry registry) {
+        return BasicLocationRegistry.isResolverPrefixForSpec(this, spec, true);
+    }
+
+    @Override
+    public Location newLocationFromString(Map locationFlags, String spec, LocationRegistry registry) {
+        ConfigBag config = extractConfig(locationFlags, spec, registry);
+        Map globalProperties = registry.getProperties();
+        String namedLocation = (String) locationFlags.get(LocationInternal.NAMED_SPEC_NAME.getName());
+        
+        if (registry != null) {
+            LocationPropertiesFromBrooklynProperties.setLocalTempDir(globalProperties, config);
+        }
+
+        return managementContext.getLocationManager().createLocation(LocationSpec.create(getLocationType())
+            .configure(config.getAllConfig())
+            .configure(LocationConfigUtils.finalAndOriginalSpecs(spec, locationFlags, globalProperties, namedLocation)));
+    }
+
+    protected ConfigBag extractConfig(Map<?,?> locationFlags, String spec, LocationRegistry registry) {
+        Map globalProperties = registry.getProperties();
+        ParsedSpec parsedSpec = specParser.parse(spec);
+        String namedLocation = (String) locationFlags.get(LocationInternal.NAMED_SPEC_NAME.getName());
+        
+        // prefer args map over location flags
+        Map<String, Object> filteredProperties = getFilteredLocationProperties(getPrefix(), namedLocation, globalProperties);
+        ConfigBag flags = ConfigBag.newInstance(parsedSpec.argsMap).putIfAbsent(locationFlags).putIfAbsent(filteredProperties);
+
+        return flags;
+    }
+    
+    protected Map<String, Object> getFilteredLocationProperties(String provider, String namedLocation, Map<String, ?> globalProperties) {
+        return new LocationPropertiesFromBrooklynProperties().getLocationProperties(getPrefix(), namedLocation, globalProperties);
+    }
+    
+    protected static class ParsedSpec {
+        public final String spec;
+        public final List<String> partsList;
+        public final Map<String,String> argsMap;
+        
+        ParsedSpec(String spec, List<String> partsList, Map<String,String> argsMap) {
+            this.spec = spec;
+            this.partsList = ImmutableList.copyOf(partsList);
+            this.argsMap = Collections.unmodifiableMap(MutableMap.copyOf(argsMap));
+        }
+    }
+    
+    /**
+     * Parses a spec, by default of the general form "prefix:parts1:part2(arg1=val1,arg2=val2)"
+     */
+    protected static class SpecParser {
+        
+        protected final String prefix;
+        protected final Pattern pattern;
+        private String exampleUsage;
+        
+        public SpecParser(String prefix) {
+            this.prefix = prefix;
+            pattern = Pattern.compile("("+prefix+"|"+prefix.toLowerCase()+"|"+prefix.toUpperCase()+")" + "(:)?" + "(\\((.*)\\))?$");
+        }
+        
+        public SpecParser(String prefix, Pattern pattern) {
+            this.prefix = prefix;
+            this.pattern = pattern;
+        }
+        
+        public SpecParser setExampleUsage(String exampleUsage) {
+            this.exampleUsage = exampleUsage;
+            return this;
+        }
+
+        protected String getUsage(String spec) {
+            if (exampleUsage == null) {
+                return "Spec should be in the form "+pattern;
+            } else {
+                return "for example, "+exampleUsage;
+            }
+        }
+        
+        protected void checkParsedSpec(ParsedSpec parsedSpec) {
+            // If someone tries "byon:(),byon:()" as a single spec, we get weird key-values!
+            for (String key : parsedSpec.argsMap.keySet()) {
+                if (key.contains(":") || key.contains("{") || key.contains("}") || key.contains("(") || key.contains(")")) {
+                    throw new IllegalArgumentException("Invalid byon spec: "+parsedSpec.spec+" (key="+key+")");
+                }
+            }
+            String name = parsedSpec.argsMap.get("name");
+            if (parsedSpec.argsMap.containsKey("name") && (name == null || name.isEmpty())) {
+                throw new IllegalArgumentException("Invalid location '"+parsedSpec.spec+"'; if name supplied then value must be non-empty");
+            }
+            String displayName = parsedSpec.argsMap.get("displayName");
+            if (parsedSpec.argsMap.containsKey("displayName") && (displayName == null || displayName.isEmpty())) {
+                throw new IllegalArgumentException("Invalid location '"+parsedSpec.spec+"'; if displayName supplied then value must be non-empty");
+            }
+        }
+        
+        public ParsedSpec parse(String spec) {
+            Matcher matcher = pattern.matcher(spec);
+            if (!matcher.matches()) {
+                throw new IllegalArgumentException("Invalid location '"+spec+"'; "+getUsage(spec));
+            }
+            
+            String argsPart = matcher.group(3);
+            if (argsPart != null && argsPart.startsWith("(") && argsPart.endsWith(")")) {
+                // TODO Hacky; hosts("1.1.1.1") returns argsPart=("1.1.1.1")
+                argsPart = argsPart.substring(1, argsPart.length()-1);
+            }
+            Map<String, String> argsMap = KeyValueParser.parseMap(argsPart);
+            ParsedSpec result = new ParsedSpec(spec, ImmutableList.<String>of(), argsMap);
+            checkParsedSpec(result);
+            return result;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/core/AggregatingMachineProvisioningLocation.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/core/AggregatingMachineProvisioningLocation.java b/core/src/main/java/org/apache/brooklyn/location/core/AggregatingMachineProvisioningLocation.java
new file mode 100644
index 0000000..6ea9ec9
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/location/core/AggregatingMachineProvisioningLocation.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.location.core;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import java.io.Closeable;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.brooklyn.api.location.MachineLocation;
+import org.apache.brooklyn.api.location.MachineProvisioningLocation;
+import org.apache.brooklyn.api.location.NoMachinesAvailableException;
+import org.apache.brooklyn.util.core.flags.SetFromFlag;
+import org.apache.brooklyn.util.stream.Streams;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+/**
+ * Takes a list of other provisioners, and round-robins across them when obtaining a machine.
+ */
+public class AggregatingMachineProvisioningLocation<T extends MachineLocation> extends AbstractLocation 
+        implements MachineProvisioningLocation<T>, Closeable {
+
+    private static final long serialVersionUID = -8818006672883481775L;
+
+    private Object lock;
+    
+    @SetFromFlag
+    protected List<MachineProvisioningLocation<T>> provisioners;
+    
+    @SetFromFlag
+    protected Map<T, MachineProvisioningLocation<T>> inUse;
+
+    protected final AtomicInteger obtainCounter = new AtomicInteger();
+    
+    public AggregatingMachineProvisioningLocation() {
+        this(Maps.newLinkedHashMap());
+    }
+    
+    public AggregatingMachineProvisioningLocation(Map properties) {
+        super(properties);
+
+        if (isLegacyConstruction()) {
+            init();
+        }
+    }
+
+    @Override
+    public void init() {
+        super.init();
+    }
+    
+    @Override
+    public String toVerboseString() {
+        return Objects.toStringHelper(this).omitNullValues()
+                .add("id", getId()).add("name", getDisplayName())
+                .add("provisioners", provisioners)
+                .toString();
+    }
+
+    @Override
+    public AbstractLocation configure(Map<?,?> properties) {
+        if (lock == null) {
+            lock = new Object();
+            provisioners = Lists.<MachineProvisioningLocation<T>>newArrayList();
+            inUse = Maps.<T, MachineProvisioningLocation<T>>newLinkedHashMap();
+        }
+        return super.configure(properties);
+    }
+    
+    @Override
+    public AggregatingMachineProvisioningLocation<T> newSubLocation(Map<?,?> newFlags) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void close() {
+        for (MachineProvisioningLocation<?> provisioner : provisioners) {
+            if (provisioner instanceof Closeable) {
+                Streams.closeQuietly((Closeable)provisioner);
+            }
+        }
+    }
+    
+    public T obtain() throws NoMachinesAvailableException {
+        return obtain(Maps.<String,Object>newLinkedHashMap());
+    }
+    
+    @Override
+    public T obtain(Map<?,?> flags) throws NoMachinesAvailableException {
+        checkState(provisioners.size() > 0, "no provisioners!");
+        int index = obtainCounter.getAndIncrement();
+        for (int i = 0; i < provisioners.size(); i++) {
+            MachineProvisioningLocation<T> provisioner = provisioners.get(index++ % provisioners.size());
+            try {
+                T machine = provisioner.obtain(flags);
+                inUse.put(machine, provisioner);
+                return machine;
+            } catch (NoMachinesAvailableException e) {
+                // move on; try next
+            }
+        }
+        throw new NoMachinesAvailableException("No machines available in "+toString());
+    }
+
+    @Override
+    public void release(T machine) {
+        MachineProvisioningLocation<T> provisioner = inUse.remove(machine);
+        if (provisioner != null) {
+            provisioner.release(machine);
+        } else {
+            throw new IllegalStateException("Request to release machine "+machine+", but this machine is not currently allocated");
+        }
+    }
+
+    @Override
+    public Map<String,Object> getProvisioningFlags(Collection<String> tags) {
+        return Maps.<String,Object>newLinkedHashMap();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/core/BasicHardwareDetails.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/core/BasicHardwareDetails.java b/core/src/main/java/org/apache/brooklyn/location/core/BasicHardwareDetails.java
new file mode 100644
index 0000000..09059ec
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/location/core/BasicHardwareDetails.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.location.core;
+
+import javax.annotation.concurrent.Immutable;
+
+import com.google.common.base.Objects;
+
+import org.apache.brooklyn.api.location.HardwareDetails;
+
+@Immutable
+public class BasicHardwareDetails implements HardwareDetails {
+
+    private final Integer cpuCount;
+    private final Integer ram;
+
+    public BasicHardwareDetails(Integer cpuCount, Integer ram) {
+        this.cpuCount = cpuCount;
+        this.ram = ram;
+    }
+
+    @Override
+    public Integer getCpuCount() {
+        return cpuCount;
+    }
+
+    @Override
+    public Integer getRam() {
+        return ram;
+    }
+
+    @Override
+    public String toString() {
+        return Objects.toStringHelper(HardwareDetails.class)
+                .omitNullValues()
+                .add("cpuCount", cpuCount)
+                .add("ram", ram)
+                .toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/core/BasicLocationDefinition.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/core/BasicLocationDefinition.java b/core/src/main/java/org/apache/brooklyn/location/core/BasicLocationDefinition.java
new file mode 100644
index 0000000..df9c641
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/location/core/BasicLocationDefinition.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.location.core;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.location.LocationDefinition;
+import org.apache.brooklyn.util.text.Identifiers;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+
+public class BasicLocationDefinition implements LocationDefinition {
+
+    private final String id;
+    private final String name;
+    private final String spec;
+    private final Map<String,Object> config;
+
+    public BasicLocationDefinition(String name, String spec, Map<String,? extends Object> config) {
+        this(Identifiers.makeRandomId(8), name, spec, config);
+    }
+    
+    public BasicLocationDefinition(String id, String name, String spec, Map<String,? extends Object> config) {      
+        this.id = Preconditions.checkNotNull(id);
+        this.name = name;
+        this.spec = Preconditions.checkNotNull(spec);
+        this.config = config==null ? ImmutableMap.<String, Object>of() : ImmutableMap.<String, Object>copyOf(config);
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public String getName() {
+        return name;
+    }
+    
+    public String getSpec() {
+        return spec;
+    }
+    
+    @Override
+    public Map<String, Object> getConfig() {
+        return config;
+    }
+    
+    @Override
+    public boolean equals(Object o) {
+        if (this==o) return true;
+        if ((o instanceof LocationDefinition) && id.equals(((LocationDefinition)o).getId())) return true;
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return "LocationDefinition{" +
+                "id='" + getId() + '\'' +
+                ", name='" + getName() + '\'' +
+                ", spec='" + getSpec() + '\'' +
+                ", config=" + getConfig() +
+                '}';
+    }
+}