brooklyn-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From henev...@apache.org
Subject [57/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)
Date Wed, 19 Aug 2015 11:10:15 GMT
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/core/BasicLocationRegistry.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/core/BasicLocationRegistry.java b/core/src/main/java/org/apache/brooklyn/location/core/BasicLocationRegistry.java
new file mode 100644
index 0000000..329c38e
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/location/core/BasicLocationRegistry.java
@@ -0,0 +1,489 @@
+/*
+ * 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.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.ServiceLoader;
+import java.util.Set;
+
+import org.apache.brooklyn.api.catalog.BrooklynCatalog;
+import org.apache.brooklyn.api.catalog.CatalogItem;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationDefinition;
+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.config.ConfigMap;
+import org.apache.brooklyn.core.catalog.CatalogPredicates;
+import org.apache.brooklyn.core.config.ConfigPredicates;
+import org.apache.brooklyn.core.config.ConfigUtils;
+import org.apache.brooklyn.core.mgmt.internal.LocalLocationManager;
+import org.apache.brooklyn.location.core.internal.LocationInternal;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.collections.MutableMap;
+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.javalang.JavaClassNames;
+import org.apache.brooklyn.util.text.Identifiers;
+import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes;
+import org.apache.brooklyn.util.text.WildcardGlobs;
+import org.apache.brooklyn.util.text.WildcardGlobs.PhraseTreatment;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Sets;
+
+/**
+ * See {@link LocationRegistry} for general description.
+ * <p>
+ * TODO The relationship between the catalog and the location registry is a bit messy.
+ * For all existing code, the location registry is the definitive way to resolve
+ * locations. 
+ * <p>
+ * Any location item added to the catalog must therefore be registered here.
+ * Whenever an item is added to the catalog, it will automatically call 
+ * {@link #updateDefinedLocation(CatalogItem)}. Similarly, when a location
+ * is deleted from the catalog it will call {@link #removeDefinedLocation(CatalogItem)}.
+ * <p>
+ * However, the location item in the catalog has an unparsed blob of YAML, which contains
+ * important things like the type and the config of the location. This is only parsed when 
+ * {@link BrooklynCatalog#createSpec(CatalogItem)} is called. We therefore jump through 
+ * some hoops to wire together the catalog and the registry.
+ * <p>
+ * To add a location to the catalog, and then to resolve a location that is in the catalog, 
+ * it goes through the following steps:
+ * 
+ * <ol>
+ *   <li>Call {@link BrooklynCatalog#addItems(String)}
+ *     <ol>
+ *       <li>This automatically calls {@link #updateDefinedLocation(CatalogItem)}
+ *       <li>A LocationDefinition is creating, using as its id the {@link CatalogItem#getSymbolicName()}.
+ *           The definition's spec is {@code brooklyn.catalog:<symbolicName>:<version>},
+ *     </ol>
+ *   <li>A blueprint can reference the catalog item using its symbolic name, 
+ *       such as the YAML {@code location: my-new-location}.
+ *       (this feels similar to the "named locations").
+ *     <ol>
+ *       <li>This automatically calls {@link #resolve(String)}.
+ *       <li>The LocationDefinition is found by lookig up this name.
+ *       <li>The {@link LocationDefiniton.getSpec()} is retrieved; the right {@link LocationResolver} is
+ *           found for it.
+ *       <li>This uses the {@link CatalogLocationResolver}, because the spec starts with {@code brooklyn.catalog:}.
+ *       <li>This resolver extracts from the spec the <symobolicName>:<version>, and looks up the 
+ *           catalog item using {@link BrooklynCatalog#getCatalogItem(String, String)}.
+ *       <li>It then creates a {@link LocationSpec} by calling {@link BrooklynCatalog#createSpec(CatalogItem)}.
+ *         <ol>
+ *           <li>This first tries to use the type (that is in the YAML) as a simple Java class.
+ *           <li>If that fails, it will resolve the type using {@link #resolve(String, Boolean, Map)}, which
+ *               returns an actual location object.
+ *           <li>It extracts from that location object the appropriate metadata to create a {@link LocationSpec},
+ *               returns the spec and discards the location object.
+ *         </ol>
+ *       <li>The resolver creates the {@link Location} from the {@link LocationSpec}
+ *     </ol>
+ * </ol>
+ * 
+ * There is no concept of a location version in this registry. The version
+ * in the catalog is generally ignored.
+ */
+@SuppressWarnings({"rawtypes","unchecked"})
+public class BasicLocationRegistry implements LocationRegistry {
+
+    // TODO save / serialize
+    // (we persist live locations, ie those in the LocationManager, but not "catalog" locations, ie those in this Registry)
+    
+    public static final Logger log = LoggerFactory.getLogger(BasicLocationRegistry.class);
+
+    /**
+     * Splits a comma-separated list of locations (names or specs) into an explicit list.
+     * The splitting is very careful to handle commas embedded within specs, to split correctly.
+     */
+    public static List<String> expandCommaSeparateLocations(String locations) {
+        return WildcardGlobs.getGlobsAfterBraceExpansion("{"+locations+"}", false, PhraseTreatment.INTERIOR_NOT_EXPANDABLE, PhraseTreatment.INTERIOR_NOT_EXPANDABLE);
+        // don't do this, it tries to expand commas inside parentheses which is not good!
+//        QuotedStringTokenizer.builder().addDelimiterChars(",").buildList((String)id);
+    }
+
+    private final ManagementContext mgmt;
+    /** map of defined locations by their ID */
+    private final Map<String,LocationDefinition> definedLocations = new LinkedHashMap<String, LocationDefinition>();
+
+    protected final Map<String,LocationResolver> resolvers = new LinkedHashMap<String, LocationResolver>();
+
+    private final Set<String> specsWarnedOnException = Sets.newConcurrentHashSet();
+
+    public BasicLocationRegistry(ManagementContext mgmt) {
+        this.mgmt = checkNotNull(mgmt, "mgmt");
+        findServices();
+        updateDefinedLocations();
+    }
+
+    protected void findServices() {
+        ServiceLoader<LocationResolver> loader = ServiceLoader.load(LocationResolver.class, mgmt.getCatalogClassLoader());
+        MutableList<LocationResolver> loadedResolvers;
+        try {
+            loadedResolvers = MutableList.copyOf(loader);
+        } catch (Throwable e) {
+            log.warn("Error loading resolvers (rethrowing): "+e);
+            throw Exceptions.propagate(e);
+        }
+        
+        for (LocationResolver r: loadedResolvers) {
+            registerResolver(r);
+        }
+        if (log.isDebugEnabled()) log.debug("Location resolvers are: "+resolvers);
+        if (resolvers.isEmpty()) log.warn("No location resolvers detected: is src/main/resources correctly included?");
+    }
+
+    /** Registers the given resolver, invoking {@link LocationResolver#init(ManagementContext)} on the argument
+     * and returning true, unless the argument indicates false for {@link LocationResolver.EnableableLocationResolver#isEnabled()} */
+    public boolean registerResolver(LocationResolver r) {
+        r.init(mgmt);
+        if (r instanceof LocationResolver.EnableableLocationResolver) {
+            if (!((LocationResolver.EnableableLocationResolver)r).isEnabled()) {
+                return false;
+            }
+        }
+        resolvers.put(r.getPrefix(), r);
+        return true;
+    }
+    
+    @Override
+    public Map<String,LocationDefinition> getDefinedLocations() {
+        synchronized (definedLocations) {
+            return ImmutableMap.<String,LocationDefinition>copyOf(definedLocations);
+        }
+    }
+    
+    @Override
+    public LocationDefinition getDefinedLocationById(String id) {
+        return definedLocations.get(id);
+    }
+
+    @Override
+    public LocationDefinition getDefinedLocationByName(String name) {
+        synchronized (definedLocations) {
+            for (LocationDefinition l: definedLocations.values()) {
+                if (l.getName().equals(name)) return l;
+            }
+            return null;
+        }
+    }
+
+    @Override
+    public void updateDefinedLocation(LocationDefinition l) {
+        synchronized (definedLocations) { 
+            definedLocations.put(l.getId(), l); 
+        }
+    }
+
+    /**
+     * Converts the given item from the catalog into a LocationDefinition, and adds it
+     * to the registry (overwriting anything already registered with the id
+     * {@link CatalogItem#getCatalogItemId()}.
+     */
+    public void updateDefinedLocation(CatalogItem<Location, LocationSpec<?>> item) {
+        String id = item.getCatalogItemId();
+        String symbolicName = item.getSymbolicName();
+        String spec = CatalogLocationResolver.NAME + ":" + id;
+        Map<String, Object> config = ImmutableMap.<String, Object>of();
+        BasicLocationDefinition locDefinition = new BasicLocationDefinition(symbolicName, symbolicName, spec, config);
+        
+        updateDefinedLocation(locDefinition);
+    }
+
+    public void removeDefinedLocation(CatalogItem<Location, LocationSpec<?>> item) {
+        removeDefinedLocation(item.getSymbolicName());
+    }
+    
+    @Override
+    public void removeDefinedLocation(String id) {
+        LocationDefinition removed;
+        synchronized (definedLocations) { 
+            removed = definedLocations.remove(id);
+        }
+        if (removed == null && log.isDebugEnabled()) {
+            log.debug("{} was asked to remove location with id {} but no such location was registered", this, id);
+        }
+    }
+    
+    public void updateDefinedLocations() {
+        synchronized (definedLocations) {
+            // first read all properties starting  brooklyn.location.named.xxx
+            // (would be nice to move to a better way, e.g. yaml, then deprecate this approach, but first
+            // we need ability/format for persisting named locations, and better support for adding+saving via REST/GUI)
+            int count = 0; 
+            String NAMED_LOCATION_PREFIX = "brooklyn.location.named.";
+            ConfigMap namedLocationProps = mgmt.getConfig().submap(ConfigPredicates.startingWith(NAMED_LOCATION_PREFIX));
+            for (String k: namedLocationProps.asMapWithStringKeys().keySet()) {
+                String name = k.substring(NAMED_LOCATION_PREFIX.length());
+                // If has a dot, then is a sub-property of a named location (e.g. brooklyn.location.named.prod1.user=bob)
+                if (!name.contains(".")) {
+                    // this is a new named location
+                    String spec = (String) namedLocationProps.asMapWithStringKeys().get(k);
+                    // make up an ID
+                    String id = Identifiers.makeRandomId(8);
+                    Map<String, Object> config = ConfigUtils.filterForPrefixAndStrip(namedLocationProps.asMapWithStringKeys(), k+".");
+                    definedLocations.put(id, new BasicLocationDefinition(id, name, spec, config));
+                    count++;
+                }
+            }
+            if (log.isDebugEnabled())
+                log.debug("Found "+count+" defined locations from properties (*.named.* syntax): "+definedLocations.values());
+            if (getDefinedLocationByName("localhost")==null && !BasicOsDetails.Factory.newLocalhostInstance().isWindows()
+                    && LocationConfigUtils.isEnabled(mgmt, "brooklyn.location.localhost")) {
+                log.debug("Adding a defined location for localhost");
+                // add 'localhost' *first*
+                ImmutableMap<String, LocationDefinition> oldDefined = ImmutableMap.copyOf(definedLocations);
+                definedLocations.clear();
+                String id = Identifiers.makeRandomId(8);
+                definedLocations.put(id, localhost(id));
+                definedLocations.putAll(oldDefined);
+            }
+            
+            for (CatalogItem<Location, LocationSpec<?>> item : mgmt.getCatalog().getCatalogItems(CatalogPredicates.IS_LOCATION)) {
+                updateDefinedLocation(item);
+                count++;
+            }
+        }
+    }
+    
+    @VisibleForTesting
+    void disablePersistence() {
+        // persistence isn't enabled yet anyway (have to manually save things,
+        // defining the format and file etc)
+    }
+
+    protected static BasicLocationDefinition localhost(String id) {
+        return new BasicLocationDefinition(id, "localhost", "localhost", null);
+    }
+    
+    /** to catch circular references */
+    protected ThreadLocal<Set<String>> specsSeen = new ThreadLocal<Set<String>>();
+    
+    @Override @Deprecated
+    public boolean canMaybeResolve(String spec) {
+        return getSpecResolver(spec) != null;
+    }
+
+    @Override
+    public final Location resolve(String spec) {
+        return resolve(spec, true, null).get();
+    }
+    
+    @Override @Deprecated
+    public final Location resolveIfPossible(String spec) {
+        if (!canMaybeResolve(spec)) return null;
+        return resolve(spec, null, null).orNull();
+    }
+    
+    @Deprecated /** since 0.7.0 not used */
+    public final Maybe<Location> resolve(String spec, boolean manage) {
+        return resolve(spec, manage, null);
+    }
+    
+    public Maybe<Location> resolve(String spec, Boolean manage, Map locationFlags) {
+        try {
+            locationFlags = MutableMap.copyOf(locationFlags);
+            if (manage!=null) {
+                locationFlags.put(LocalLocationManager.CREATE_UNMANAGED, !manage);
+            }
+            
+            Set<String> seenSoFar = specsSeen.get();
+            if (seenSoFar==null) {
+                seenSoFar = new LinkedHashSet<String>();
+                specsSeen.set(seenSoFar);
+            }
+            if (seenSoFar.contains(spec))
+                return Maybe.absent(Suppliers.ofInstance(new IllegalStateException("Circular reference in definition of location '"+spec+"' ("+seenSoFar+")")));
+            seenSoFar.add(spec);
+            
+            LocationResolver resolver = getSpecResolver(spec);
+
+            if (resolver != null) {
+                try {
+                    return Maybe.of(resolver.newLocationFromString(locationFlags, spec, this));
+                } catch (RuntimeException e) {
+                    return Maybe.absent(Suppliers.ofInstance(e));
+                }
+            }
+
+            // problem: but let's ensure that classpath is sane to give better errors in common IDE bogus case;
+            // and avoid repeated logging
+            String errmsg;
+            if (spec == null || specsWarnedOnException.add(spec)) {
+                if (resolvers.get("id")==null || resolvers.get("named")==null) {
+                    log.error("Standard location resolvers not installed, location resolution will fail shortly. "
+                            + "This usually indicates a classpath problem, such as when running from an IDE which "
+                            + "has not properly copied META-INF/services from src/main/resources. "
+                            + "Known resolvers are: "+resolvers.keySet());
+                    errmsg = "Unresolvable location '"+spec+"': "
+                            + "Problem detected with location resolver configuration; "
+                            + resolvers.keySet()+" are the only available location resolvers. "
+                            + "More information can be found in the logs.";
+                } else {
+                    log.debug("Location resolution failed for '"+spec+"' (if this is being loaded it will fail shortly): known resolvers are: "+resolvers.keySet());
+                    errmsg = "Unknown location '"+spec+"': "
+                            + "either this location is not recognised or there is a problem with location resolver configuration.";
+                }
+            } else {
+                // For helpful log message construction: assumes classpath will not suddenly become wrong; might happen with OSGi though!
+                if (log.isDebugEnabled()) log.debug("Location resolution failed again for '"+spec+"' (throwing)");
+                errmsg = "Unknown location '"+spec+"': "
+                        + "either this location is not recognised or there is a problem with location resolver configuration.";
+            }
+
+            return Maybe.absent(Suppliers.ofInstance(new NoSuchElementException(errmsg)));
+
+        } finally {
+            specsSeen.remove();
+        }
+    }
+
+    @Override
+    public final Location resolve(String spec, Map locationFlags) {
+        return resolve(spec, null, locationFlags).get();
+    }
+
+    protected LocationResolver getSpecResolver(String spec) {
+        int colonIndex = spec.indexOf(':');
+        int bracketIndex = spec.indexOf("(");
+        int dividerIndex = (colonIndex < 0) ? bracketIndex : (bracketIndex < 0 ? colonIndex : Math.min(bracketIndex, colonIndex));
+        String prefix = dividerIndex >= 0 ? spec.substring(0, dividerIndex) : spec;
+        LocationResolver resolver = resolvers.get(prefix);
+       
+        if (resolver == null)
+            resolver = getSpecDefaultResolver(spec);
+        
+        return resolver;
+    }
+    
+    protected LocationResolver getSpecDefaultResolver(String spec) {
+        return getSpecFirstResolver(spec, "id", "named", "jclouds");
+    }
+    protected LocationResolver getSpecFirstResolver(String spec, String ...resolversToCheck) {
+        for (String resolverId: resolversToCheck) {
+            LocationResolver resolver = resolvers.get(resolverId);
+            if (resolver!=null && resolver.accepts(spec, this))
+                return resolver;
+        }
+        return null;
+    }
+
+    /** providers default impl for RegistryLocationResolver.accepts */
+    public static boolean isResolverPrefixForSpec(LocationResolver resolver, String spec, boolean argumentRequired) {
+        if (spec==null) return false;
+        if (spec.startsWith(resolver.getPrefix()+":")) return true;
+        if (!argumentRequired && spec.equals(resolver.getPrefix())) return true;
+        return false;
+    }
+
+    @Override
+    public List<Location> resolve(Iterable<?> spec) {
+        List<Location> result = new ArrayList<Location>();
+        for (Object id : spec) {
+            if (id instanceof String) {
+                result.add(resolve((String) id));
+            } else if (id instanceof Location) {
+                result.add((Location) id);
+            } else {
+                if (id instanceof Iterable)
+                    throw new IllegalArgumentException("Cannot resolve '"+id+"' to a location; collections of collections not allowed"); 
+                throw new IllegalArgumentException("Cannot resolve '"+id+"' to a location; unsupported type "+
+                        (id == null ? "null" : id.getClass().getName())); 
+            }
+        }
+        return result;
+    }
+    
+    public List<Location> resolveList(Object l) {
+        if (l==null) l = Collections.emptyList();
+        if (l instanceof String) l = JavaStringEscapes.unwrapJsonishListIfPossible((String)l);
+        if (l instanceof Iterable) return resolve((Iterable<?>)l);
+        throw new IllegalArgumentException("Location list must be supplied as a collection or a string, not "+
+            JavaClassNames.simpleClassName(l)+"/"+l);
+    }
+    
+    @Override
+    public Location resolve(LocationDefinition ld) {
+        return resolve(ld, null, null).get();
+    }
+
+    @Override @Deprecated
+    public Location resolveForPeeking(LocationDefinition ld) {
+        // TODO should clean up how locations are stored, figuring out whether they are shared or not;
+        // or maybe better, the API calls to this might just want to get the LocationSpec objects back
+        
+        // for now we use a 'CREATE_UNMANGED' flag to prevent management (leaks and logging)
+        return resolve(ld, ConfigBag.newInstance().configure(LocalLocationManager.CREATE_UNMANAGED, true).getAllConfig());
+    }
+
+    @Override @Deprecated
+    public Location resolve(LocationDefinition ld, Map<?,?> flags) {
+        return resolveLocationDefinition(ld, flags, null);
+    }
+    
+    /** @deprecated since 0.7.0 not used (and optionalName was ignored anyway) */
+    @Deprecated
+    public Location resolveLocationDefinition(LocationDefinition ld, Map locationFlags, String optionalName) {
+        return resolve(ld, null, locationFlags).get();
+    }
+    
+    public Maybe<Location> resolve(LocationDefinition ld, Boolean manage, Map locationFlags) {
+        ConfigBag newLocationFlags = ConfigBag.newInstance(ld.getConfig())
+            .putAll(locationFlags)
+            .putIfAbsentAndNotNull(LocationInternal.NAMED_SPEC_NAME, ld.getName())
+            .putIfAbsentAndNotNull(LocationInternal.ORIGINAL_SPEC, ld.getName());
+        Maybe<Location> result = resolve(ld.getSpec(), manage, newLocationFlags.getAllConfigRaw());
+        if (result.isPresent()) 
+            return result;
+        throw new IllegalStateException("Cannot instantiate location '"+ld+"' pointing at "+ld.getSpec()+": "+
+            Exceptions.collapseText( ((Absent<?>)result).getException() ));
+    }
+
+    @Override
+    public Map getProperties() {
+        return mgmt.getConfig().asMapWithStringKeys();
+    }
+
+    @VisibleForTesting
+    public static void setupLocationRegistryForTesting(ManagementContext mgmt) {
+        // ensure localhost is added (even on windows)
+        LocationDefinition l = mgmt.getLocationRegistry().getDefinedLocationByName("localhost");
+        if (l==null) mgmt.getLocationRegistry().updateDefinedLocation(
+                BasicLocationRegistry.localhost(Identifiers.makeRandomId(8)) );
+        
+        ((BasicLocationRegistry)mgmt.getLocationRegistry()).disablePersistence();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/core/BasicMachineDetails.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/core/BasicMachineDetails.java b/core/src/main/java/org/apache/brooklyn/location/core/BasicMachineDetails.java
new file mode 100644
index 0000000..0c323ed
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/location/core/BasicMachineDetails.java
@@ -0,0 +1,183 @@
+/*
+ * 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.io.BufferedReader;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.Immutable;
+
+import org.apache.brooklyn.api.location.HardwareDetails;
+import org.apache.brooklyn.api.location.MachineDetails;
+import org.apache.brooklyn.api.location.OsDetails;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.util.core.ResourceUtils;
+import org.apache.brooklyn.util.core.task.DynamicTasks;
+import org.apache.brooklyn.util.core.task.TaskTags;
+import org.apache.brooklyn.util.core.task.ssh.internal.PlainSshExecTaskFactory;
+import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
+import org.apache.brooklyn.util.stream.Streams;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Objects;
+import com.google.common.base.Splitter;
+import com.google.common.base.Throwables;
+import com.google.common.collect.Maps;
+import com.google.common.io.CharStreams;
+
+@Immutable
+public class BasicMachineDetails implements MachineDetails {
+
+    public static final Logger LOG = LoggerFactory.getLogger(BasicMachineDetails.class);
+
+    private final HardwareDetails hardwareDetails;
+    private final OsDetails osDetails;
+
+    public BasicMachineDetails(HardwareDetails hardwareDetails, OsDetails osDetails) {
+        this.hardwareDetails = checkNotNull(hardwareDetails, "hardwareDetails");
+        this.osDetails = checkNotNull(osDetails, "osDetails");
+    }
+
+    @Nonnull
+    @Override
+    public HardwareDetails getHardwareDetails() {
+        return hardwareDetails;
+    }
+
+    @Nonnull
+    @Override
+    public OsDetails getOsDetails() {
+        return osDetails;
+    }
+
+    @Override
+    public String toString() {
+        return Objects.toStringHelper(MachineDetails.class)
+                .add("os", osDetails)
+                .add("hardware", hardwareDetails)
+                .toString();
+    }
+
+    /**
+     * Creates a MachineDetails for the given location by SSHing to the machine and
+     * running a Bash script to gather data. Should only be called from within a
+     * task context. If this might not be the case then use {@link
+     * #taskForSshMachineLocation(SshMachineLocation)} instead.
+     */
+    @Beta
+    public static BasicMachineDetails forSshMachineLocationLive(SshMachineLocation location) {
+        return TaskTags.markInessential(DynamicTasks.queueIfPossible(taskForSshMachineLocation(location))
+                .orSubmitAsync()
+                .asTask())
+                .getUnchecked();
+    }
+
+    /**
+     * @return A task that gathers machine details by SSHing to the machine and running
+     *         a Bash script to gather data.
+     */
+    public static Task<BasicMachineDetails> taskForSshMachineLocation(SshMachineLocation location) {
+        BufferedReader reader = new BufferedReader(Streams.reader(
+                new ResourceUtils(BasicMachineDetails.class).getResourceFromUrl(
+                        "classpath://org/apache/brooklyn/location/basic/os-details.sh")));
+        List<String> script;
+        try {
+            script = CharStreams.readLines(reader);
+        } catch (IOException e) {
+            LOG.error("Error reading os-details script", e);
+            throw Throwables.propagate(e);
+        } finally {
+            try {
+                reader.close();
+            } catch (IOException e) {
+                // Not rethrowing e because it might obscure an exception caught by the first catch
+                LOG.error("Error closing os-details script reader", e);
+            }
+        }
+        Task<BasicMachineDetails> task = new PlainSshExecTaskFactory<String>(location, script)
+                .summary("Getting machine details for: " + location)
+                .requiringZeroAndReturningStdout()
+                .returning(taskToMachineDetailsFunction(location))
+                .newTask()
+                .asTask();
+
+        return task;
+    }
+
+    private static Function<ProcessTaskWrapper<?>, BasicMachineDetails> taskToMachineDetailsFunction(final SshMachineLocation location) {
+        return new Function<ProcessTaskWrapper<?>, BasicMachineDetails>() {
+            @Override
+            public BasicMachineDetails apply(ProcessTaskWrapper<?> input) {
+                if (input.getExitCode() != 0) {
+                    LOG.warn("Non-zero exit code when fetching machine details for {}; guessing anonymous linux", location);
+                    return new BasicMachineDetails(new BasicHardwareDetails(null, null),
+                            BasicOsDetails.Factory.ANONYMOUS_LINUX);
+                }
+
+                String stdout = input.getStdout();
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("Found following details at {}: {}", location, stdout);
+                }
+
+                Map<String,String> details = Maps.newHashMap(Splitter.on(CharMatcher.anyOf("\r\n"))
+                        .omitEmptyStrings()
+                        .withKeyValueSeparator(":")
+                        .split(stdout));
+
+                String name = details.remove("name");
+                String version = details.remove("version");
+                String architecture = details.remove("architecture");
+                Integer ram = intOrNull(details, "ram");
+                Integer cpuCount = intOrNull(details, "cpus");
+                if (!details.isEmpty()) {
+                    LOG.debug("Unused keys from os-details script: " + Joiner.on(", ").join(details.keySet()));
+                }
+
+                OsDetails osDetails = new BasicOsDetails(name, architecture, version);
+                HardwareDetails hardwareDetails = new BasicHardwareDetails(cpuCount, ram);
+                BasicMachineDetails machineDetails = new BasicMachineDetails(hardwareDetails, osDetails);
+
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Machine details for {}: {}", location, machineDetails);
+
+                return machineDetails;
+            }
+
+            private Integer intOrNull(Map<String, String> details, String key) {
+                try {
+                    return Integer.valueOf(details.remove(key));
+                } catch (NumberFormatException e) {
+                    return null;
+                }
+            }
+        };
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/core/BasicMachineMetadata.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/core/BasicMachineMetadata.java b/core/src/main/java/org/apache/brooklyn/location/core/BasicMachineMetadata.java
new file mode 100644
index 0000000..37b61ee
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/location/core/BasicMachineMetadata.java
@@ -0,0 +1,84 @@
+/*
+ * 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 com.google.common.base.Objects;
+
+import org.apache.brooklyn.api.location.MachineManagementMixins;
+
+public class BasicMachineMetadata implements MachineManagementMixins.MachineMetadata {
+
+    final String id, name, primaryIp;
+    final Boolean isRunning;
+    final Object originalMetadata;
+    
+    public BasicMachineMetadata(String id, String name, String primaryIp, Boolean isRunning, Object originalMetadata) {
+        super();
+        this.id = id;
+        this.name = name;
+        this.primaryIp = primaryIp;
+        this.isRunning = isRunning;
+        this.originalMetadata = originalMetadata;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getPrimaryIp() {
+        return primaryIp;
+    }
+
+    public Boolean isRunning() {
+        return isRunning;
+    }
+
+    public Object getOriginalMetadata() {
+        return originalMetadata;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(id, isRunning, name, originalMetadata, primaryIp);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        if (obj == null) return false;
+        if (getClass() != obj.getClass()) return false;
+        BasicMachineMetadata other = (BasicMachineMetadata) obj;
+        if (!Objects.equal(id, other.id)) return false;
+        if (!Objects.equal(name, other.name)) return false;
+        if (!Objects.equal(primaryIp, other.primaryIp)) return false;
+        if (!Objects.equal(isRunning, other.isRunning)) return false;
+        if (!Objects.equal(originalMetadata, other.originalMetadata)) return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return Objects.toStringHelper(this).add("id", id).add("name", name).add("originalMetadata", originalMetadata).toString();
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/core/BasicOsDetails.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/core/BasicOsDetails.java b/core/src/main/java/org/apache/brooklyn/location/core/BasicOsDetails.java
new file mode 100644
index 0000000..089df03
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/location/core/BasicOsDetails.java
@@ -0,0 +1,123 @@
+/*
+ * 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.regex.Pattern;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+import com.google.common.base.Objects;
+
+import org.apache.brooklyn.api.location.OsDetails;
+
+@Immutable
+public class BasicOsDetails implements OsDetails {
+
+    final String name, arch, version;
+    final boolean is64bit;
+    // (?i) forces matches to be case insensitive
+    public static final String UNIX_OS_NAME_PATTERNS = "(?i).*linux.*|centos|debian|fedora|gentoo|rhel|slackware|solaris|suse|ubuntu|coreos";
+
+    /** Sets is64Bit according to value of arch parameter. */
+    public BasicOsDetails(String name, String arch, String version) {
+       this(name, arch, version, arch != null && arch.contains("64"));
+    }
+
+    public BasicOsDetails(String name, String arch, String version, boolean is64Bit) {
+        this.name = name; this.arch = arch; this.version = version; this.is64bit = is64Bit;
+    }
+    
+    // TODO: Should be replaced with an enum like Jclouds' OsFamily and isX methods should
+    // switch against known cases
+    @Nullable
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Nullable
+    @Override
+    public String getArch() {
+        return arch;
+    }
+
+    @Nullable
+    @Override
+    public String getVersion() {
+        return version;
+    }
+
+    @Override
+    public boolean isWindows() {
+        //TODO confirm
+        return getName()!=null && getName().toLowerCase().contains("microsoft");
+    }
+
+    @Override
+    public boolean isLinux() {
+        return getName() != null && Pattern.matches(UNIX_OS_NAME_PATTERNS, getName());
+    }
+
+    @Override
+    public boolean isMac() {
+        return getName()!=null && getName().equals(OsNames.MAC_OS_X);
+    }
+
+    @Override
+    public boolean is64bit() {
+        return is64bit;
+    }
+
+    @Override
+    public String toString() {
+        return Objects.toStringHelper(OsDetails.class)
+                .omitNullValues()
+                .add("name", name)
+                .add("version", version)
+                .add("arch", arch)
+                .toString();
+    }
+
+    public static class OsNames {
+        public static final String MAC_OS_X = "Mac OS X";
+    }
+    
+    public static class OsArchs {
+        public static final String X_86_64 = "x86_64";
+//        public static final String X_86 = "x86";
+//        // is this standard?  or do we ever need the above?
+        public static final String I386 = "i386";
+    }
+
+    public static class OsVersions {
+        public static final String MAC_10_8 = "10.8";
+        public static final String MAC_10_9 = "10.9";
+    }
+    
+    public static class Factory {
+        public static OsDetails newLocalhostInstance() {
+            return new BasicOsDetails(System.getProperty("os.name"), System.getProperty("os.arch"), System.getProperty("os.version"));
+        }
+        
+        public static final OsDetails ANONYMOUS_LINUX = new BasicOsDetails("linux", OsArchs.I386, "unknown");
+        public static final OsDetails ANONYMOUS_LINUX_64 = new BasicOsDetails("linux", OsArchs.X_86_64, "unknown");
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/core/CatalogLocationResolver.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/core/CatalogLocationResolver.java b/core/src/main/java/org/apache/brooklyn/location/core/CatalogLocationResolver.java
new file mode 100644
index 0000000..a907a49
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/location/core/CatalogLocationResolver.java
@@ -0,0 +1,79 @@
+/*
+ * 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.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.brooklyn.api.catalog.CatalogItem;
+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.core.catalog.internal.CatalogUtils;
+
+/**
+ * Given a location spec in the form {@code brooklyn.catalog:<symbolicName>:<version>}, 
+ * looks up the catalog to get its definition and creates such a location.
+ */
+public class CatalogLocationResolver implements LocationResolver {
+
+    @SuppressWarnings("unused")
+    private static final Logger log = LoggerFactory.getLogger(CatalogLocationResolver.class);
+
+    public static final String NAME = "brooklyn.catalog";
+
+    private ManagementContext managementContext;
+
+    @Override
+    public void init(ManagementContext managementContext) {
+        this.managementContext = checkNotNull(managementContext, "managementContext");
+    }
+    
+    @Override
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public Location newLocationFromString(Map locationFlags, String spec, LocationRegistry registry) {
+        String id = spec.substring(NAME.length()+1);
+        CatalogItem<?, ?> item = CatalogUtils.getCatalogItemOptionalVersion(managementContext, id);
+        LocationSpec<?> origLocSpec = managementContext.getCatalog().createSpec((CatalogItem<Location, LocationSpec<?>>)item);
+        LocationSpec<?> locSpec = LocationSpec.create(origLocSpec)
+                .configure(locationFlags);
+        return managementContext.getLocationManager().createLocation(locSpec);
+    }
+
+    @Override
+    public String getPrefix() {
+        return NAME;
+    }
+    
+    /**
+     * accepts anything that looks like it will be a YAML catalog item (e.g. starting "brooklyn.locations")
+     */
+    @Override
+    public boolean accepts(String spec, LocationRegistry registry) {
+        if (BasicLocationRegistry.isResolverPrefixForSpec(this, spec, false)) return true;
+        if (registry.getDefinedLocationByName(spec)!=null) return true;
+        return false;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/core/DefinedLocationByIdResolver.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/core/DefinedLocationByIdResolver.java b/core/src/main/java/org/apache/brooklyn/location/core/DefinedLocationByIdResolver.java
new file mode 100644
index 0000000..efe0aff
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/location/core/DefinedLocationByIdResolver.java
@@ -0,0 +1,74 @@
+/*
+ * 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.Map;
+
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationDefinition;
+import org.apache.brooklyn.api.location.LocationRegistry;
+import org.apache.brooklyn.api.location.LocationResolver;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * looks up based on ID in DefinedLocations map
+ */
+public class DefinedLocationByIdResolver implements LocationResolver {
+
+    public static final Logger log = LoggerFactory.getLogger(DefinedLocationByIdResolver.class);
+
+    public static final String ID = "id";
+    
+    private volatile ManagementContext managementContext;
+
+    @Override
+    public void init(ManagementContext managementContext) {
+        this.managementContext = checkNotNull(managementContext, "managementContext");
+    }
+    
+    @SuppressWarnings({ "rawtypes" })
+    @Override
+    public Location newLocationFromString(Map locationFlags, String spec, LocationRegistry registry) {
+        String id = spec;
+        if (spec.toLowerCase().startsWith(ID+":")) {
+            id = spec.substring( (ID+":").length() );
+        }
+        LocationDefinition ld = registry.getDefinedLocationById(id);
+        ld.getSpec();
+        return ((BasicLocationRegistry)registry).resolveLocationDefinition(ld, locationFlags, null);
+    }
+
+    @Override
+    public String getPrefix() {
+        return ID;
+    }
+    
+    /** accepts anything starting  id:xxx  or just   xxx where xxx is a defined location ID */
+    @Override
+    public boolean accepts(String spec, LocationRegistry registry) {
+        if (BasicLocationRegistry.isResolverPrefixForSpec(this, spec, false)) return true;
+        if (registry.getDefinedLocationById(spec)!=null) return true;
+        return false;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/core/DeprecatedKeysMappingBuilder.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/core/DeprecatedKeysMappingBuilder.java b/core/src/main/java/org/apache/brooklyn/location/core/DeprecatedKeysMappingBuilder.java
new file mode 100644
index 0000000..ad98f04
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/location/core/DeprecatedKeysMappingBuilder.java
@@ -0,0 +1,66 @@
+/*
+ * 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.config.ConfigKey;
+import org.slf4j.Logger;
+
+import com.google.common.base.CaseFormat;
+import com.google.common.collect.ImmutableMap;
+
+/**
+* @deprecated since 0.6; for use only in converting deprecated flags; will be deleted in future version.
+*/
+public class DeprecatedKeysMappingBuilder {
+    private final ImmutableMap.Builder<String,String> builder = new ImmutableMap.Builder<String,String>();
+    private final Logger logger;
+    
+    public DeprecatedKeysMappingBuilder(Logger logger) {
+        this.logger = logger;
+    }
+
+    public DeprecatedKeysMappingBuilder camelToHyphen(ConfigKey<?> key) {
+        return camelToHyphen(key.getName());
+    }
+    
+    public DeprecatedKeysMappingBuilder camelToHyphen(String key) {
+        String hyphen = toHyphen(key);
+        if (key.equals(hyphen)) {
+            logger.warn("Invalid attempt to convert camel-case key {} to deprecated hyphen-case: both the same", hyphen);
+        } else {
+            builder.put(hyphen, key);
+        }
+        return this;
+    }
+    
+    public DeprecatedKeysMappingBuilder putAll(Map<String,String> vals) {
+        builder.putAll(vals);
+        return this;
+    }
+
+    public Map<String,String> build() {
+        return builder.build();
+    }
+    
+    private String toHyphen(String word) {
+        return CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, word);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/core/HasSubnetHostname.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/core/HasSubnetHostname.java b/core/src/main/java/org/apache/brooklyn/location/core/HasSubnetHostname.java
new file mode 100644
index 0000000..604d5ef
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/location/core/HasSubnetHostname.java
@@ -0,0 +1,32 @@
+/*
+ * 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 com.google.common.annotations.Beta;
+
+@Beta
+public interface HasSubnetHostname {
+
+    /** returns a hostname for use internally within a subnet / VPC */
+    @Beta
+    String getSubnetHostname();
+
+    /** returns an IP for use internally within a subnet / VPC */
+    String getSubnetIp();
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/core/LocationConfigKeys.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/core/LocationConfigKeys.java b/core/src/main/java/org/apache/brooklyn/location/core/LocationConfigKeys.java
new file mode 100644
index 0000000..689a1d7
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/location/core/LocationConfigKeys.java
@@ -0,0 +1,79 @@
+/*
+ * 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.io.File;
+import java.util.Set;
+
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.BasicConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.util.os.Os;
+
+import com.google.common.base.CaseFormat;
+import com.google.common.reflect.TypeToken;
+
+public class LocationConfigKeys {
+
+    public static final ConfigKey<String> LOCATION_ID = ConfigKeys.newStringConfigKey("id");
+    public static final ConfigKey<String> DISPLAY_NAME = ConfigKeys.newStringConfigKey("displayName");
+    public static final ConfigKey<Boolean> ENABLED = ConfigKeys.newBooleanConfigKey("enabled", "Whether the location is enabled for listing and use "
+        + "(only supported for selected locations)", true);
+    
+    public static final ConfigKey<String> ACCESS_IDENTITY = ConfigKeys.newStringConfigKey("identity"); 
+    public static final ConfigKey<String> ACCESS_CREDENTIAL = ConfigKeys.newStringConfigKey("credential"); 
+
+    public static final ConfigKey<Double> LATITUDE = new BasicConfigKey<Double>(Double.class, "latitude"); 
+    public static final ConfigKey<Double> LONGITUDE = new BasicConfigKey<Double>(Double.class, "longitude"); 
+
+    public static final ConfigKey<String> CLOUD_PROVIDER = ConfigKeys.newStringConfigKey("provider");
+    public static final ConfigKey<String> CLOUD_ENDPOINT = ConfigKeys.newStringConfigKey("endpoint");
+    public static final ConfigKey<String> CLOUD_REGION_ID = ConfigKeys.newStringConfigKey("region");
+    public static final ConfigKey<String> CLOUD_AVAILABILITY_ZONE_ID = ConfigKeys.newStringConfigKey("availabilityZone");
+
+    @SuppressWarnings("serial")
+    public static final ConfigKey<Set<String>> ISO_3166 = ConfigKeys.newConfigKey(new TypeToken<Set<String>>() {}, "iso3166", "ISO-3166 or ISO-3166-2 location codes"); 
+
+    public static final ConfigKey<String> USER = ConfigKeys.newStringConfigKey("user", 
+            "user account for normal access to the remote machine, defaulting to local user", System.getProperty("user.name"));
+    
+    public static final ConfigKey<String> PASSWORD = ConfigKeys.newStringConfigKey("password", "password to use for ssh; note some images do not allow password-based ssh access");
+    public static final ConfigKey<String> PUBLIC_KEY_FILE = ConfigKeys.newStringConfigKey("publicKeyFile", "ssh public key file to use; if blank will infer from privateKeyFile by appending \".pub\"");
+    public static final ConfigKey<String> PUBLIC_KEY_DATA = ConfigKeys.newStringConfigKey("publicKeyData", "ssh public key string to use (takes precedence over publicKeyFile)");
+    public static final ConfigKey<String> PRIVATE_KEY_FILE = ConfigKeys.newStringConfigKey("privateKeyFile", "a '" + File.pathSeparator + "' separated list of ssh private key files; uses first in list that can be read",
+                                                                                           Os.fromHome(".ssh/id_rsa") + File.pathSeparator + Os.fromHome(".ssh/id_dsa"));
+    public static final ConfigKey<String> PRIVATE_KEY_DATA = ConfigKeys.newStringConfigKey("privateKeyData", "ssh private key string to use (takes precedence over privateKeyFile)");
+    public static final ConfigKey<String> PRIVATE_KEY_PASSPHRASE = ConfigKeys.newStringConfigKey("privateKeyPassphrase");
+
+    /** @deprecated since 0.6.0; included here so it gets picked up in auto-detect routines */ @Deprecated
+    public static final ConfigKey<String> LEGACY_PUBLIC_KEY_FILE = ConfigKeys.convert(PUBLIC_KEY_FILE, CaseFormat.LOWER_CAMEL, CaseFormat.LOWER_HYPHEN);
+    /** @deprecated since 0.6.0; included here so it gets picked up in auto-detect routines */ @Deprecated
+    public static final ConfigKey<String> LEGACY_PUBLIC_KEY_DATA = ConfigKeys.convert(PUBLIC_KEY_DATA, CaseFormat.LOWER_CAMEL, CaseFormat.LOWER_HYPHEN);
+    /** @deprecated since 0.6.0; included here so it gets picked up in auto-detect routines */ @Deprecated
+    public static final ConfigKey<String> LEGACY_PRIVATE_KEY_FILE = ConfigKeys.convert(PRIVATE_KEY_FILE, CaseFormat.LOWER_CAMEL, CaseFormat.LOWER_HYPHEN);
+    /** @deprecated since 0.6.0; included here so it gets picked up in auto-detect routines */ @Deprecated
+    public static final ConfigKey<String> LEGACY_PRIVATE_KEY_DATA = ConfigKeys.convert(PRIVATE_KEY_DATA, CaseFormat.LOWER_CAMEL, CaseFormat.LOWER_HYPHEN);
+    /** @deprecated since 0.6.0; included here so it gets picked up in auto-detect routines */ @Deprecated
+    public static final ConfigKey<String> LEGACY_PRIVATE_KEY_PASSPHRASE = ConfigKeys.convert(PRIVATE_KEY_PASSPHRASE, CaseFormat.LOWER_CAMEL, CaseFormat.LOWER_HYPHEN);
+
+    public static final ConfigKey<Object> CALLER_CONTEXT = new BasicConfigKey<Object>(Object.class, "callerContext",
+            "An object whose toString is used for logging, to indicate wherefore a VM is being created");
+    public static final ConfigKey<String> CLOUD_MACHINE_NAMER_CLASS = ConfigKeys.newStringConfigKey("cloudMachineNamer", "fully qualified class name of a class that extends CloudMachineNamer and has a single-parameter constructor that takes a ConfigBag");
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/core/LocationConfigUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/core/LocationConfigUtils.java b/core/src/main/java/org/apache/brooklyn/location/core/LocationConfigUtils.java
new file mode 100644
index 0000000..80879b9
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/location/core/LocationConfigUtils.java
@@ -0,0 +1,559 @@
+/*
+ * 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 org.apache.brooklyn.util.JavaGroovyEquivalents.groovyTruth;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.internal.BrooklynFeatureEnablement;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.brooklyn.location.cloud.CloudLocationConfig;
+import org.apache.brooklyn.location.core.internal.LocationInternal;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.collections.MutableSet;
+import org.apache.brooklyn.util.core.ResourceUtils;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.crypto.SecureKeys;
+import org.apache.brooklyn.util.core.crypto.SecureKeys.PassphraseProblem;
+import org.apache.brooklyn.util.crypto.AuthorizedKeysParser;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.os.Os;
+import org.apache.brooklyn.util.text.StringFunctions;
+import org.apache.brooklyn.util.text.Strings;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+public class LocationConfigUtils {
+
+    private static final Logger log = LoggerFactory.getLogger(LocationConfigUtils.class);
+
+    /** Creates an instance of {@link OsCredential} by inspecting {@link LocationConfigKeys#PASSWORD}; 
+     * {@link LocationConfigKeys#PRIVATE_KEY_DATA} and {@link LocationConfigKeys#PRIVATE_KEY_FILE};
+     * {@link LocationConfigKeys#PRIVATE_KEY_PASSPHRASE} if needed, and
+     * {@link LocationConfigKeys#PRIVATE_KEY_DATA} and {@link LocationConfigKeys#PRIVATE_KEY_FILE}
+     * (defaulting to the private key file + ".pub"). 
+     **/
+    public static OsCredential getOsCredential(ConfigBag config) {
+        return OsCredential.newInstance(config);
+    }
+    
+    /** Convenience class for holding private/public keys and passwords, inferring from config keys.
+     * See {@link LocationConfigUtils#getOsCredential(ConfigBag)}. */
+    @Beta // would be nice to replace with a builder pattern 
+    public static class OsCredential {
+        private final ConfigBag config;
+        private boolean preferPassword = false;
+        private boolean tryDefaultKeys = true;
+        private boolean requirePublicKey = true;
+        private boolean doKeyValidation = BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_VALIDATE_LOCATION_SSH_KEYS);
+        private boolean warnOnErrors = true;
+        private boolean throwOnErrors = false;
+        
+        private boolean dirty = true;;
+        
+        private String privateKeyData;
+        private String publicKeyData;
+        private String password;
+        
+        private OsCredential(ConfigBag config) {
+            this.config = config;
+        }
+
+        /** throws if there are any problems */
+        public OsCredential checkNotEmpty() {
+            checkNoErrors();
+            
+            if (!hasKey() && !hasPassword()) {
+                if (warningMessages.size()>0)
+                    throw new IllegalStateException("Could not find credentials: "+warningMessages);
+                else 
+                    throw new IllegalStateException("Could not find credentials");
+            }
+            return this;
+        }
+
+        /** throws if there were errors resolving (e.g. explicit keys, none of which were found/valid, or public key required and not found) 
+         * @return */
+        public OsCredential checkNoErrors() {
+            throwOnErrors(true);
+            dirty();
+            infer();
+            return this;
+        }
+        
+        public OsCredential logAnyWarnings() {
+            if (!warningMessages.isEmpty())
+                log.warn("When reading credentials: "+warningMessages);
+            return this;
+        }
+
+        public Set<String> getWarningMessages() {
+            return warningMessages;
+        }
+        
+        /** returns either the key or password or null; if both a key and a password this prefers the key unless otherwise set
+         * via {@link #preferPassword()} */
+        public synchronized String getPreferredCredential() {
+            infer();
+            
+            if (isUsingPassword()) return password;
+            if (hasKey()) return privateKeyData;
+            return null;
+        }
+
+        /** if there is no credential (ignores public key) */
+        public boolean isEmpty() {
+            return !hasKey() && !hasPassword();
+        }
+        public boolean hasKey() {
+            infer();
+            // key has stricter non-blank check than password
+            return Strings.isNonBlank(privateKeyData);
+        }
+        public boolean hasPassword() {
+            infer();
+            // blank, even empty passwords are allowed
+            return password!=null;
+        }
+        /** if a password is available, and either this is preferred over a key or there is no key */
+        public boolean isUsingPassword() {
+            return hasPassword() && (!hasKey() || preferPassword);
+        }
+        
+        public String getPrivateKeyData() {
+            infer();
+            return privateKeyData;
+        }
+        public String getPublicKeyData() {
+            infer();
+            return publicKeyData;
+        }
+        public String getPassword() {
+            infer();
+            return password;
+        }
+        
+        /** if both key and password supplied, prefer the key; the default */
+        public OsCredential preferKey() { preferPassword = false; return dirty(); }
+        /** if both key and password supplied, prefer the password; see {@link #preferKey()} */
+        public OsCredential preferPassword() { preferPassword = true; return dirty(); }
+        
+        /** if false, do not mind if there is no public key corresponding to any private key;
+         * defaults to true; only applies if a private key is set */
+        public OsCredential requirePublicKey(boolean requirePublicKey) {
+            this.requirePublicKey = requirePublicKey;
+            return dirty(); 
+        }
+        /** whether to check the private/public keys and passphrase are coherent; default true */
+        public OsCredential doKeyValidation(boolean doKeyValidation) {
+            this.doKeyValidation = doKeyValidation;
+            return dirty();
+        }
+        /** if true (the default) this will look at default locations set on keys */
+        public OsCredential useDefaultKeys(boolean tryDefaultKeys) {
+            this.tryDefaultKeys = tryDefaultKeys;
+            return dirty(); 
+        }
+        /** whether to log warnings on problems */
+        public OsCredential warnOnErrors(boolean warnOnErrors) {
+            this.warnOnErrors = warnOnErrors;
+            return dirty(); 
+        }
+        /** whether to throw on problems */
+        public OsCredential throwOnErrors(boolean throwOnErrors) {
+            this.throwOnErrors = throwOnErrors;
+            return dirty(); 
+        }
+        
+        private OsCredential dirty() { dirty = true; return this; }
+            
+        public static OsCredential newInstance(ConfigBag config) {
+            return new OsCredential(config);
+        }
+        
+        private synchronized void infer() {
+            if (!dirty) return;
+            warningMessages.clear(); 
+            
+            log.debug("Inferring OS credentials");
+            privateKeyData = config.get(LocationConfigKeys.PRIVATE_KEY_DATA);
+            password = config.get(LocationConfigKeys.PASSWORD);
+            publicKeyData = getKeyDataFromDataKeyOrFileKey(config, LocationConfigKeys.PUBLIC_KEY_DATA, LocationConfigKeys.PUBLIC_KEY_FILE);
+
+            KeyPair privateKey = null;
+            
+            if (Strings.isBlank(privateKeyData)) {
+                // look up private key files
+                String privateKeyFiles = null;
+                boolean privateKeyFilesExplicitlySet = config.containsKey(LocationConfigKeys.PRIVATE_KEY_FILE);
+                if (privateKeyFilesExplicitlySet || (tryDefaultKeys && password==null)) 
+                    privateKeyFiles = config.get(LocationConfigKeys.PRIVATE_KEY_FILE);
+                if (Strings.isNonBlank(privateKeyFiles)) {
+                    Iterator<String> fi = Arrays.asList(privateKeyFiles.split(File.pathSeparator)).iterator();
+                    while (fi.hasNext()) {
+                        String file = fi.next();
+                        if (Strings.isNonBlank(file)) {
+                            try {
+                                // real URL's won't actual work, due to use of path separator above 
+                                // not real important, but we get it for free if "files" is a list instead.
+                                // using ResourceUtils is useful for classpath resources
+                                if (file!=null)
+                                    privateKeyData = ResourceUtils.create().getResourceAsString(file);
+                                // else use data already set
+                                
+                                privateKey = getValidatedPrivateKey(file);
+                                
+                                if (privateKeyData==null) {
+                                    // was cleared due to validation error
+                                } else if (Strings.isNonBlank(publicKeyData)) {
+                                    log.debug("Loaded private key data from "+file+" (public key data explicitly set)");
+                                    break;
+                                } else {
+                                    String publicKeyFile = (file!=null ? file+".pub" : "(data)");
+                                    try {
+                                        publicKeyData = ResourceUtils.create().getResourceAsString(publicKeyFile);
+                                        
+                                        log.debug("Loaded private key data from "+file+
+                                            " and public key data from "+publicKeyFile);
+                                        break;
+                                    } catch (Exception e) {
+                                        Exceptions.propagateIfFatal(e);
+                                        log.debug("No public key file "+publicKeyFile+"; will try extracting from private key");
+                                        publicKeyData = AuthorizedKeysParser.encodePublicKey(privateKey.getPublic());
+                                        
+                                        if (publicKeyData==null) {
+                                            if (requirePublicKey) {
+                                                addWarning("Unable to find or extract public key for "+file, "skipping");
+                                            } else {
+                                                log.debug("Loaded private key data from "+file+" (public key data not found but not required)");
+                                                break;
+                                            }
+                                        } else {
+                                            log.debug("Loaded private key data from "+file+" (public key data extracted)");
+                                            break;
+                                        }
+                                        privateKeyData = null;
+                                    }
+                                }
+
+                            } catch (Exception e) {
+                                Exceptions.propagateIfFatal(e);
+                                String message = "Missing/invalid private key file "+file;
+                                if (privateKeyFilesExplicitlySet) addWarning(message, (!fi.hasNext() ? "no more files to try" : "trying next file")+": "+e);
+                            }
+                        }
+                    }
+                    if (privateKeyFilesExplicitlySet && Strings.isBlank(privateKeyData))
+                        error("No valid private keys found", ""+warningMessages);
+                }
+            } else {
+                privateKey = getValidatedPrivateKey("(data)");
+            }
+            
+            if (privateKeyData!=null) {
+                if (requirePublicKey && Strings.isBlank(publicKeyData)) {
+                    if (privateKey!=null) {
+                        publicKeyData = AuthorizedKeysParser.encodePublicKey(privateKey.getPublic());
+                    }
+                    if (Strings.isBlank(publicKeyData)) {
+                        error("If explicit "+LocationConfigKeys.PRIVATE_KEY_DATA.getName()+" is supplied, then "
+                            + "the corresponding "+LocationConfigKeys.PUBLIC_KEY_DATA.getName()+" must also be supplied.", null);
+                    } else {
+                        log.debug("Public key data extracted");
+                    }
+                }
+                if (doKeyValidation && privateKey!=null && privateKey.getPublic()!=null && Strings.isNonBlank(publicKeyData)) {
+                    PublicKey decoded = null;
+                    try {
+                        decoded = AuthorizedKeysParser.decodePublicKey(publicKeyData);
+                    } catch (Exception e) {
+                        Exceptions.propagateIfFatal(e);
+                        addWarning("Invalid public key: "+decoded);
+                    }
+                    if (decoded!=null && !privateKey.getPublic().equals( decoded )) {
+                        error("Public key inferred from does not match public key extracted from private key", null);
+                    }
+                }
+            }
+
+            log.debug("OS credential inference: "+this);
+            dirty = false;
+        }
+
+        private KeyPair getValidatedPrivateKey(String label) {
+            KeyPair privateKey = null;
+            String passphrase = config.get(CloudLocationConfig.PRIVATE_KEY_PASSPHRASE);
+            try {
+                privateKey = SecureKeys.readPem(new ByteArrayInputStream(privateKeyData.getBytes()), passphrase);
+                if (passphrase!=null) {
+                    // get the unencrypted key data for our internal use (jclouds requires this)
+                    privateKeyData = SecureKeys.toPem(privateKey);
+                }
+            } catch (PassphraseProblem e) {
+                if (doKeyValidation) {
+                    log.debug("Encountered error handling key "+label+": "+e, e);
+                    if (Strings.isBlank(passphrase))
+                        addWarning("Passphrase required for key '"+label+"'");
+                    else
+                        addWarning("Invalid passphrase for key '"+label+"'");
+                    privateKeyData = null;
+                }
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                if (doKeyValidation) {
+                    addWarning("Unable to parse private key from '"+label+"': unknown format");
+                    privateKeyData = null;
+                }
+            }
+            return privateKey;
+        }
+        
+        Set<String> warningMessages = MutableSet.of();
+        
+        private void error(String msg, String logExtension) {
+            addWarning(msg);
+            if (warnOnErrors) log.warn(msg+(logExtension==null ? "" : ": "+logExtension));
+            if (throwOnErrors) throw new IllegalStateException(msg+(logExtension==null ? "" : "; "+logExtension));
+        }
+
+        private void addWarning(String msg) {
+            addWarning(msg, null);
+        }
+        private void addWarning(String msg, String debugExtension) {
+            log.debug(msg+(debugExtension==null ? "" : "; "+debugExtension));
+            warningMessages.add(msg);
+        }
+
+        @Override
+        public String toString() {
+            return getClass().getSimpleName()+"["+
+                (Strings.isNonBlank(publicKeyData) ? publicKeyData : "no-public-key")+";"+
+                (Strings.isNonBlank(privateKeyData) ? "private-key-present" : "no-private-key")+","+
+                (password!=null ? "password(len="+password.length()+")" : "no-password")+"]";
+        }
+    }
+
+    /** @deprecated since 0.7.0, use #getOsCredential(ConfigBag) */ @Deprecated
+    public static String getPrivateKeyData(ConfigBag config) {
+        return getKeyData(config, LocationConfigKeys.PRIVATE_KEY_DATA, LocationConfigKeys.PRIVATE_KEY_FILE);
+    }
+    
+    /** @deprecated since 0.7.0, use #getOsCredential(ConfigBag) */ @Deprecated
+    public static String getPublicKeyData(ConfigBag config) {
+        String data = getKeyData(config, LocationConfigKeys.PUBLIC_KEY_DATA, LocationConfigKeys.PUBLIC_KEY_FILE);
+        if (groovyTruth(data)) return data;
+        
+        String privateKeyFile = config.get(LocationConfigKeys.PRIVATE_KEY_FILE);
+        if (groovyTruth(privateKeyFile)) {
+            List<String> privateKeyFiles = Arrays.asList(privateKeyFile.split(File.pathSeparator));
+            List<String> publicKeyFiles = ImmutableList.copyOf(Iterables.transform(privateKeyFiles, StringFunctions.append(".pub")));
+            List<String> publicKeyFilesTidied = tidyFilePaths(publicKeyFiles);
+            
+            String fileData = getFileContents(publicKeyFilesTidied);
+            if (groovyTruth(fileData)) {
+                if (log.isDebugEnabled()) log.debug("Loaded "+LocationConfigKeys.PUBLIC_KEY_DATA.getName()+" from inferred files, based on "+LocationConfigKeys.PRIVATE_KEY_FILE.getName() + ": used " + publicKeyFilesTidied + " for "+config.getDescription());
+                config.put(LocationConfigKeys.PUBLIC_KEY_DATA, fileData);
+                return fileData;
+            } else {
+                log.info("Not able to load "+LocationConfigKeys.PUBLIC_KEY_DATA.getName()+" from inferred files, based on "+LocationConfigKeys.PRIVATE_KEY_FILE.getName() + ": tried " + publicKeyFilesTidied + " for "+config.getDescription());
+            }
+        }
+        
+        return null;
+    }
+
+    /** @deprecated since 0.7.0, use #getOsCredential(ConfigBag) */ @Deprecated
+    public static String getKeyData(ConfigBag config, ConfigKey<String> dataKey, ConfigKey<String> fileKey) {
+        return getKeyDataFromDataKeyOrFileKey(config, dataKey, fileKey);
+    }
+    
+    private static String getKeyDataFromDataKeyOrFileKey(ConfigBag config, ConfigKey<String> dataKey, ConfigKey<String> fileKey) {
+        boolean unused = config.isUnused(dataKey);
+        String data = config.get(dataKey);
+        if (groovyTruth(data) && !unused) {
+            return data;
+        }
+        
+        String file = config.get(fileKey);
+        if (groovyTruth(file)) {
+            List<String> files = Arrays.asList(file.split(File.pathSeparator));
+            List<String> filesTidied = tidyFilePaths(files);
+            String fileData = getFileContents(filesTidied);
+            if (fileData == null) {
+                log.warn("Invalid file" + (files.size() > 1 ? "s" : "") + " for " + fileKey + " (given " + files + 
+                        (files.equals(filesTidied) ? "" : "; converted to " + filesTidied) + ") " +
+                        "may fail provisioning " + config.getDescription());
+            } else if (groovyTruth(data)) {
+                if (!fileData.trim().equals(data.trim()))
+                    log.warn(dataKey.getName()+" and "+fileKey.getName()+" both specified; preferring the former");
+            } else {
+                data = fileData;
+                config.put(dataKey, data);
+                config.get(dataKey);
+            }
+        }
+        
+        return data;
+    }
+    
+    /**
+     * Reads the given file(s) in-order, returning the contents of the first file that can be read.
+     * Returns the file contents, or null if none of the files can be read.
+     *  
+     * @param files             list of file paths
+     */
+    private static String getFileContents(Iterable<String> files) {
+        Iterator<String> fi = files.iterator();
+        while (fi.hasNext()) {
+            String file = fi.next();
+            if (groovyTruth(file)) {
+                try {
+                    // see comment above
+                    String result = ResourceUtils.create().getResourceAsString(file);
+                    if (result!=null) return result;
+                    log.debug("Invalid file "+file+" ; " + (!fi.hasNext() ? "no more files to try" : "trying next file")+" (null)");
+                } catch (Exception e) {
+                    Exceptions.propagateIfFatal(e);
+                    log.debug("Invalid file "+file+" ; " + (!fi.hasNext() ? "no more files to try" : "trying next file"), e);
+                }
+            }
+        }
+        return null;
+    }
+
+    private static List<String> tidyFilePaths(Iterable<String> files) {
+        List<String> result = Lists.newArrayList();
+        for (String file : files) {
+            result.add(Os.tidyPath(file));
+        }
+        return result;
+    }
+
+    /** @deprecated since 0.6.0 use configBag.getWithDeprecation */
+    @Deprecated
+    @SuppressWarnings("unchecked")
+    public static <T> T getConfigCheckingDeprecatedAlternatives(ConfigBag configBag, ConfigKey<T> preferredKey,
+            ConfigKey<?> ...deprecatedKeys) {
+        T value1 = (T) configBag.getWithDeprecation(preferredKey, deprecatedKeys);
+        T value2 = getConfigCheckingDeprecatedAlternativesInternal(configBag, preferredKey, deprecatedKeys);
+        if (!Objects.equal(value1, value2)) {
+            // points to a bug in one of the get-with-deprecation methods
+            log.warn("Deprecated getConfig with deprecated keys "+Arrays.toString(deprecatedKeys)+" gets different value with " +
+                    "new strategy "+preferredKey+" ("+value1+") and old ("+value2+"); preferring old value for now, but this behaviour will change");
+            return value2;
+        }
+        return value1;
+    }
+    
+    @SuppressWarnings("unchecked")
+    private static <T> T getConfigCheckingDeprecatedAlternativesInternal(ConfigBag configBag, ConfigKey<T> preferredKey,
+            ConfigKey<?> ...deprecatedKeys) {
+        ConfigKey<?> keyProvidingValue = null;
+        T value = null;
+        boolean found = false;
+        if (configBag.containsKey(preferredKey)) {
+            value = configBag.get(preferredKey);
+            found = true;
+            keyProvidingValue = preferredKey;
+        }
+        
+        for (ConfigKey<?> deprecatedKey: deprecatedKeys) {
+            T altValue = null;
+            boolean altFound = false;
+            if (configBag.containsKey(deprecatedKey)) {
+                altValue = (T) configBag.get(deprecatedKey);
+                altFound = true;
+                
+                if (altFound) {
+                    if (found) {
+                        if (Objects.equal(value, altValue)) {
+                            // fine -- nothing
+                        } else {
+                            log.warn("Detected deprecated key "+deprecatedKey+" with value "+altValue+" used in addition to "+keyProvidingValue+" " +
+                                    "with value "+value+" for "+configBag.getDescription()+"; ignoring");
+                            configBag.remove(deprecatedKey);
+                        }
+                    } else {
+                        log.warn("Detected deprecated key "+deprecatedKey+" with value "+altValue+" used instead of recommended "+preferredKey+"; " +
+                                "promoting to preferred key status; will not be supported in future versions");
+                        configBag.put(preferredKey, altValue);
+                        configBag.remove(deprecatedKey);
+                        value = altValue;
+                        found = true;
+                        keyProvidingValue = deprecatedKey;
+                    }
+                }
+            }
+        }
+        
+        if (found) {
+            return value;
+        } else {
+            return configBag.get(preferredKey); // get the default
+        }
+    }
+
+    public static Map<ConfigKey<String>,String> finalAndOriginalSpecs(String finalSpec, Object ...sourcesForOriginalSpec) {
+        // yuck!: TODO should clean up how these things get passed around
+        Map<ConfigKey<String>,String> result = MutableMap.of();
+        if (finalSpec!=null) 
+            result.put(LocationInternal.FINAL_SPEC, finalSpec);
+        
+        String originalSpec = null;
+        for (Object source: sourcesForOriginalSpec) {
+            if (source instanceof CharSequence) originalSpec = source.toString();
+            else if (source instanceof Map) {
+                if (originalSpec==null) originalSpec = Strings.toString( ((Map<?,?>)source).get(LocationInternal.ORIGINAL_SPEC) );
+                if (originalSpec==null) originalSpec = Strings.toString( ((Map<?,?>)source).get(LocationInternal.ORIGINAL_SPEC.getName()) );
+            }
+            if (originalSpec!=null) break; 
+        }
+        if (originalSpec==null) originalSpec = finalSpec;
+        if (originalSpec!=null)
+            result.put(LocationInternal.ORIGINAL_SPEC, originalSpec);
+        
+        return result;
+    }
+
+    public static boolean isEnabled(ManagementContext mgmt, String prefix) {
+        ConfigKey<Boolean> key = ConfigKeys.newConfigKeyWithPrefix(prefix+".", LocationConfigKeys.ENABLED);
+        Boolean enabled = mgmt.getConfig().getConfig(key);
+        if (enabled!=null) return enabled.booleanValue();
+        return true;
+    }
+    
+
+}


Mime
View raw message