http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/core/LocationPredicates.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/core/LocationPredicates.java b/core/src/main/java/org/apache/brooklyn/location/core/LocationPredicates.java new file mode 100644 index 0000000..3491dc4 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/core/LocationPredicates.java @@ -0,0 +1,108 @@ +/* + * 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.Nullable; + +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.config.ConfigKey.HasConfigKey; + +import com.google.common.base.Objects; +import com.google.common.base.Predicate; + +public class LocationPredicates { + + public static <T> Predicate<Location> idEqualTo(final T val) { + return new Predicate<Location>() { + @Override + public boolean apply(@Nullable Location input) { + return (input != null) && Objects.equal(input.getId(), val); + } + }; + } + + public static <T> Predicate<Location> displayNameEqualTo(final T val) { + return new Predicate<Location>() { + @Override + public boolean apply(@Nullable Location input) { + return (input != null) && Objects.equal(input.getDisplayName(), val); + } + }; + } + + public static <T> Predicate<Location> configEqualTo(final ConfigKey<T> configKey, final T val) { + return new Predicate<Location>() { + @Override + public boolean apply(@Nullable Location input) { + return (input != null) && Objects.equal(input.getConfig(configKey), val); + } + }; + } + + public static <T> Predicate<Location> configEqualTo(final HasConfigKey<T> configKey, final T val) { + return new Predicate<Location>() { + @Override + public boolean apply(@Nullable Location input) { + return (input != null) && Objects.equal(input.getConfig(configKey), val); + } + }; + } + + /** + * Returns a predicate that determines if a given location is a direct child of this {@code parent}. + */ + public static <T> Predicate<Location> isChildOf(final Location parent) { + return new Predicate<Location>() { + @Override + public boolean apply(@Nullable Location input) { + return (input != null) && Objects.equal(input.getParent(), parent); + } + }; + } + + /** + * Returns a predicate that determines if a given location is a descendant of this {@code ancestor}. + */ + public static <T> Predicate<Location> isDescendantOf(final Location ancestor) { + return new Predicate<Location>() { + @Override + public boolean apply(@Nullable Location input) { + // assumes impossible to have cycles in location-hierarchy + Location contenderAncestor = (input == null) ? input : input.getParent(); + while (contenderAncestor != null) { + if (Objects.equal(contenderAncestor, ancestor)) { + return true; + } + contenderAncestor = contenderAncestor.getParent(); + } + return false; + } + }; + } + + public static <T> Predicate<Location> managed() { + return new Predicate<Location>() { + @Override + public boolean apply(@Nullable Location input) { + return (input != null) && Locations.isManaged(input); + } + }; + } +}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/core/LocationPropertiesFromBrooklynProperties.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/core/LocationPropertiesFromBrooklynProperties.java b/core/src/main/java/org/apache/brooklyn/location/core/LocationPropertiesFromBrooklynProperties.java new file mode 100644 index 0000000..fd1b92a --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/core/LocationPropertiesFromBrooklynProperties.java @@ -0,0 +1,223 @@ +/* + * 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 java.io.File; +import java.util.Map; + +import org.apache.brooklyn.core.config.ConfigUtils; +import org.apache.brooklyn.core.internal.BrooklynProperties; +import org.apache.brooklyn.core.server.BrooklynServerConfig; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.core.internal.ssh.SshTool; +import org.apache.brooklyn.util.os.Os; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Predicates; +import com.google.common.base.Strings; +import com.google.common.collect.Maps; + +/** + * The properties to use for locations, loaded from brooklyn.properties file. + * + * @author aledsage + **/ +public class LocationPropertiesFromBrooklynProperties { + + private static final Logger LOG = LoggerFactory.getLogger(LocationPropertiesFromBrooklynProperties.class); + + @SuppressWarnings("deprecation") + protected static final Map<String, String> DEPRECATED_KEYS_MAPPING = new DeprecatedKeysMappingBuilder(LOG) + .camelToHyphen(LocationConfigKeys.DISPLAY_NAME) + .camelToHyphen(LocationConfigKeys.PRIVATE_KEY_FILE) + .camelToHyphen(LocationConfigKeys.PRIVATE_KEY_DATA) + .camelToHyphen(LocationConfigKeys.PRIVATE_KEY_PASSPHRASE) + .camelToHyphen(LocationConfigKeys.PUBLIC_KEY_FILE) + .camelToHyphen(LocationConfigKeys.PUBLIC_KEY_DATA) + .camelToHyphen(LocationConfigKeys.CALLER_CONTEXT) + .build(); + + /** + * Finds the properties that apply to location, stripping off the prefixes. + * + * Order of preference (in ascending order) is: + * <ol> + * <li>brooklyn.location.* + * <li>brooklyn.location.provider.* + * <li>brooklyn.location.named.namedlocation.* + * </ol> + * <p> + * Converts deprecated hyphenated properties to the non-deprecated camelCase format. + */ + public Map<String, Object> getLocationProperties(String provider, String namedLocation, Map<String, ?> properties) { + ConfigBag result = ConfigBag.newInstance(); + + if (!Strings.isNullOrEmpty(provider)) + result.put(LocationConfigKeys.CLOUD_PROVIDER, provider); + // named properties are preferred over providerOrApi properties + result.putAll(transformDeprecated(getGenericLocationSingleWordProperties(properties))); + if (!Strings.isNullOrEmpty(provider)) result.putAll(transformDeprecated(getScopedLocationProperties(provider, properties))); + if (!Strings.isNullOrEmpty(namedLocation)) result.putAll(transformDeprecated(getNamedLocationProperties(namedLocation, properties))); + + setLocalTempDir(properties, result); + + return result.getAllConfigRaw(); + } + + /** allow the temp dir where ssh temporary files on the brooklyn server side are placed */ + public static void setLocalTempDir(Map<String,?> source, ConfigBag target) { + // TODO better would be to use BrooklynServerConfig, requiring management passed in + String brooklynDataDir = (String) source.get(BrooklynServerConfig.getMgmtBaseDir(source)); + if (brooklynDataDir != null && brooklynDataDir.length() > 0) { + String tempDir = Os.mergePaths(brooklynDataDir, "tmp", "ssh"); + target.putIfAbsentAndNotNull(SshTool.PROP_LOCAL_TEMP_DIR, tempDir); + Os.deleteOnExitEmptyParentsUpTo(new File(tempDir), new File(brooklynDataDir)); + } + } + + /** + * Gets the named provider (e.g. if using a property like {@code brooklyn.location.named.myfavourite=localhost}, then + * {@code getNamedProvider("myfavourite", properties)} will return {@code "localhost"}). + */ + protected String getNamedProvider(String namedLocation, Map<String, ?> properties) { + String key = String.format("brooklyn.location.named.%s", namedLocation); + return (String) properties.get(key); + } + + /** + * Returns those properties in the form "brooklyn.location.xyz", where "xyz" is any + * key that does not contain dots. We do this special (sub-optimal!) filtering + * because we want to exclude brooklyn.location.named.*, brooklyn.location.jclouds.*, etc. + * We only want those properties that are to be generic for all locations. + * + * Strips off the prefix in the returned map. + */ + protected Map<String, Object> getGenericLocationSingleWordProperties(Map<String, ?> properties) { + return getMatchingSingleWordProperties("brooklyn.location.", properties); + } + + /** + * Gets all properties that start with {@code "brooklyn.location."+scopeSuffix+"."}, stripping off + * the prefix in the returned map. + */ + protected Map<String, Object> getScopedLocationProperties(String scopeSuffix, Map<String, ?> properties) { + checkArgument(!scopeSuffix.startsWith("."), "scopeSuffix \"%s\" should not start with \".\"", scopeSuffix); + checkArgument(!scopeSuffix.endsWith("."), "scopeSuffix \"%s\" should not end with \".\"", scopeSuffix); + String prefix = String.format("brooklyn.location.%s.", scopeSuffix); + return ConfigUtils.filterForPrefixAndStrip(properties, prefix).asMapWithStringKeys(); + } + + /** + * Gets all properties that start with the given {@code fullPrefix}, stripping off + * the prefix in the returned map. + */ + protected Map<String, Object> getMatchingProperties(String fullPrefix, Map<String, ?> properties) { + return ConfigUtils.filterForPrefixAndStrip(properties, fullPrefix).asMapWithStringKeys(); + } + + /** + * Gets all properties that start with either of the given prefixes. The {@code fullPreferredPrefix} + * properties will override any duplicates in {@code fullDeprecatedPrefix}. If there are any + * properties that match the {@code fullDeprecatedPrefix}, then a warning will be logged. + * + * @see #getMatchingProperties(String, Map) + */ + protected Map<String, Object> getMatchingProperties(String fullPreferredPrefix, String fullDeprecatedPrefix, Map<String, ?> properties) { + Map<String, Object> deprecatedResults = getMatchingProperties(fullDeprecatedPrefix, properties); + Map<String, Object> results = getMatchingProperties(fullPreferredPrefix, properties); + + if (deprecatedResults.size() > 0) { + LOG.warn("Deprecated use of properties prefix "+fullDeprecatedPrefix+"; instead use "+fullPreferredPrefix); + return MutableMap.<String, Object>builder() + .putAll(deprecatedResults) + .putAll(results) + .build(); + } else { + return results; + } + } + + /** + * Gets all properties that start with the given {@code fullPrefix}, stripping off + * the prefix in the returned map. + * + * Returns only those properties whose key suffix is a single word (i.e. contains no dots). + * We do this special (sub-optimal!) filtering because we want sub-scoped things + * (e.g. could want brooklyn.location.privateKeyFile, but not brooklyn.location.named.*). + */ + protected Map<String, Object> getMatchingSingleWordProperties(String fullPrefix, Map<String, ?> properties) { + BrooklynProperties filteredProperties = ConfigUtils.filterForPrefixAndStrip(properties, fullPrefix); + return ConfigUtils.filterFor(filteredProperties, Predicates.not(Predicates.containsPattern("\\."))).asMapWithStringKeys(); + } + + /** + * Gets all single-word properties that start with either of the given prefixes. The {@code fullPreferredPrefix} + * properties will override any duplicates in {@code fullDeprecatedPrefix}. If there are any + * properties that match the {@code fullDeprecatedPrefix}, then a warning will be logged. + * + * @see #getMatchingSingleWordProperties(String, Map) + */ + protected Map<String, Object> getMatchingSingleWordProperties(String fullPreferredPrefix, String fullDeprecatedPrefix, Map<String, ?> properties) { + Map<String, Object> deprecatedResults = getMatchingSingleWordProperties(fullDeprecatedPrefix, properties); + Map<String, Object> results = getMatchingSingleWordProperties(fullPreferredPrefix, properties); + + if (deprecatedResults.size() > 0) { + LOG.warn("Deprecated use of properties prefix "+fullDeprecatedPrefix+"; instead use "+fullPreferredPrefix); + return MutableMap.<String, Object>builder() + .putAll(deprecatedResults) + .putAll(results) + .build(); + } else { + return results; + } + } + + protected Map<String, Object> getNamedLocationProperties(String locationName, Map<String, ?> properties) { + checkArgument(!Strings.isNullOrEmpty(locationName), "locationName should not be blank"); + String prefix = String.format("brooklyn.location.named.%s.", locationName); + return ConfigUtils.filterForPrefixAndStrip(properties, prefix).asMapWithStringKeys(); + } + + protected Map<String, Object> transformDeprecated(Map<String, ?> properties) { + Map<String,Object> result = Maps.newLinkedHashMap(); + Map<String, String> deprecatedKeysMapping = getDeprecatedKeysMapping(); + + for (Map.Entry<String,?> entry : properties.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + if (deprecatedKeysMapping.containsKey(key)) { + String transformedKey = deprecatedKeysMapping.get(key); + LOG.warn("Deprecated key {}, transformed to {}; will not be supported in future versions", new Object[] {key, transformedKey}); + result.put(transformedKey, value); + } else { + result.put(key, value); + } + } + + return result; + } + + protected Map<String,String> getDeprecatedKeysMapping() { + return DEPRECATED_KEYS_MAPPING; + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/core/Locations.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/core/Locations.java b/core/src/main/java/org/apache/brooklyn/location/core/Locations.java new file mode 100644 index 0000000..663624f --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/core/Locations.java @@ -0,0 +1,160 @@ +/* + * 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.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.brooklyn.api.entity.Entity; +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.mgmt.LocationManager; +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.entity.core.Entities; +import org.apache.brooklyn.location.core.internal.LocationInternal; +import org.apache.brooklyn.location.ssh.SshMachineLocation; +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.yaml.Yamls; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableList; + +public class Locations { + + private static final Logger log = LoggerFactory.getLogger(Locations.class); + + public static final LocationsFilter USE_FIRST_LOCATION = new LocationsFilter() { + private static final long serialVersionUID = 3100091615409115890L; + + @Override + public List<Location> filterForContext(List<Location> locations, Object context) { + if (locations.size()<=1) return locations; + return ImmutableList.of(locations.get(0)); + } + }; + + public interface LocationsFilter extends Serializable { + public List<Location> filterForContext(List<Location> locations, Object context); + } + + /** as {@link Machines#findUniqueMachineLocation(Iterable)} */ + public static Maybe<MachineLocation> findUniqueMachineLocation(Iterable<? extends Location> locations) { + return Machines.findUniqueMachineLocation(locations); + } + + /** as {@link Machines#findUniqueSshMachineLocation(Iterable)} */ + public static Maybe<SshMachineLocation> findUniqueSshMachineLocation(Iterable<? extends Location> locations) { + return Machines.findUniqueSshMachineLocation(locations); + } + + /** if no locations are supplied, returns locations on the entity, or in the ancestors, until it finds a non-empty set, + * or ultimately the empty set if no locations are anywhere */ + public static Collection<? extends Location> getLocationsCheckingAncestors(Collection<? extends Location> locations, Entity entity) { + // look in ancestors if location not set here + Entity ancestor = entity; + while ((locations==null || locations.isEmpty()) && ancestor!=null) { + locations = ancestor.getLocations(); + ancestor = ancestor.getParent(); + } + return locations; + } + + public static boolean isManaged(Location loc) { + ManagementContext mgmt = ((LocationInternal)loc).getManagementContext(); + return (mgmt != null) && mgmt.isRunning() && mgmt.getLocationManager().isManaged(loc); + } + + public static void unmanage(Location loc) { + if (isManaged(loc)) { + ManagementContext mgmt = ((LocationInternal)loc).getManagementContext(); + mgmt.getLocationManager().unmanage(loc); + } + } + + /** + * Registers the given location (and all its children) with the management context. + * @throws IllegalStateException if the parent location is not already managed + * + * @since 0.6.0 (added only for backwards compatibility, where locations are being created directly; previously in {@link Entities}). + * @deprecated in 0.6.0; use {@link LocationManager#createLocation(LocationSpec)} instead. + */ + public static void manage(Location loc, ManagementContext managementContext) { + if (!managementContext.getLocationManager().isManaged(loc)) { + log.warn("Deprecated use of unmanaged location ("+loc+"); will be managed automatically now but not supported in future versions"); + // FIXME this occurs MOST OF THE TIME e.g. including BrooklynLauncher.location(locationString) + // not sure what is the recommend way to convert from locationString to locationSpec, or the API we want to expose; + // deprecating some of the LocationRegistry methods seems sensible? + log.debug("Stack trace for location of: Deprecated use of unmanaged location; will be managed automatically now but not supported in future versions", new Exception("TRACE for: Deprecated use of unmanaged location")); + managementContext.getLocationManager().manage(loc); + } + } + + public static Location coerce(ManagementContext mgmt, Object rawO) { + if (rawO==null) + return null; + if (rawO instanceof Location) + return (Location)rawO; + + Object raw = rawO; + if (raw instanceof String) + raw = Yamls.parseAll((String)raw).iterator().next(); + + String name; + Map<?, ?> flags = null; + if (raw instanceof Map) { + // for yaml, take the key, and merge with locationFlags + Map<?,?> tm = ((Map<?,?>)raw); + if (tm.size()!=1) { + throw new IllegalArgumentException("Location "+rawO+" is invalid; maps must have only one key, being the location spec string"); + } + name = (String) tm.keySet().iterator().next(); + flags = (Map<?, ?>) tm.values().iterator().next(); + + } else if (raw instanceof String) { + name = (String)raw; + + } else { + throw new IllegalArgumentException("Location "+rawO+" is invalid; can only parse strings or maps"); + } + return mgmt.getLocationRegistry().resolve(name, flags); + } + + public static Collection<? extends Location> coerceToCollection(ManagementContext mgmt, Object rawO) { + if (rawO==null) return null; + Object raw = rawO; + if (raw instanceof Collection) { + List<Location> result = MutableList.<Location>of(); + for (Object o: (Collection<?>)raw) + result.add(coerce(mgmt, o)); + return result; + } + if (raw instanceof String) { + raw = Yamls.parseAll((String)raw).iterator().next(); + if (raw instanceof Collection) + return coerceToCollection(mgmt, raw); + } + return Collections.singletonList( coerce(mgmt, raw) ); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/core/Machines.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/core/Machines.java b/core/src/main/java/org/apache/brooklyn/location/core/Machines.java new file mode 100644 index 0000000..164e4ae --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/core/Machines.java @@ -0,0 +1,191 @@ +/* + * 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.net.InetAddress; +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.location.MachineLocation; +import org.apache.brooklyn.entity.core.Attributes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.Iterables; + +import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation; +import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation.LocalhostMachine; +import org.apache.brooklyn.location.ssh.SshMachineLocation; +import org.apache.brooklyn.location.winrm.WinRmMachineLocation; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.net.HasNetworkAddresses; + +/** utilities for working with MachineLocations */ +public class Machines { + + private static final Logger log = LoggerFactory.getLogger(Machines.class); + + public static Maybe<String> getSubnetHostname(Location where) { + // TODO Should we look at HasNetworkAddresses? But that's not a hostname. + String hostname = null; + if (where instanceof HasSubnetHostname) { + hostname = ((HasSubnetHostname) where).getSubnetHostname(); + } + if (hostname == null && where instanceof MachineLocation) { + InetAddress addr = ((MachineLocation) where).getAddress(); + if (addr != null) hostname = addr.getHostAddress(); + } + log.debug("computed subnet hostname {} for {}", hostname, where); + // TODO if Maybe.absent(message) appears, could/should use that + // TODO If no machine available, should we throw new IllegalStateException("Cannot find hostname for "+where); + return Maybe.fromNullable(hostname); + } + + public static Maybe<String> getSubnetIp(Location where) { + // TODO Too much duplication between the ip and hostname methods + String result = null; + if (where instanceof HasSubnetHostname) { + result = ((HasSubnetHostname) where).getSubnetIp(); + } + if (where instanceof HasNetworkAddresses) { + Set<String> privateAddrs = ((HasNetworkAddresses) where).getPrivateAddresses(); + if (privateAddrs.size() > 0) { + result = Iterables.get(privateAddrs, 0); + } + } + if (result == null && where instanceof MachineLocation) { + InetAddress addr = ((MachineLocation) where).getAddress(); + if (addr != null) result = addr.getHostAddress(); + } + log.debug("computed subnet host ip {} for {}", result, where); + return Maybe.fromNullable(result); + } + + @SuppressWarnings("unchecked") + public static <T> Maybe<T> findUniqueElement(Iterable<?> items, Class<T> type) { + if (items==null) return null; + Iterator<?> i = items.iterator(); + T result = null; + while (i.hasNext()) { + Object candidate = i.next(); + if (type.isInstance(candidate)) { + if (result==null) result = (T)candidate; + else { + if (log.isTraceEnabled()) + log.trace("Multiple instances of "+type+" in "+items+"; ignoring"); + return Maybe.absent(new IllegalStateException("Multiple instances of "+type+" in "+items+"; expected a single one")); + } + } + } + if (result==null) + return Maybe.absent(new IllegalStateException("No instances of "+type+" available (in "+items+")")); + return Maybe.of(result); + } + + public static Maybe<MachineLocation> findUniqueMachineLocation(Iterable<? extends Location> locations) { + return findUniqueElement(locations, MachineLocation.class); + } + + public static Maybe<SshMachineLocation> findUniqueSshMachineLocation(Iterable<? extends Location> locations) { + return findUniqueElement(locations, SshMachineLocation.class); + } + + public static Maybe<WinRmMachineLocation> findUniqueWinRmMachineLocation(Iterable<? extends Location> locations) { + return findUniqueElement(locations, WinRmMachineLocation.class); + } + + public static Maybe<String> findSubnetHostname(Iterable<? extends Location> ll) { + Maybe<MachineLocation> l = findUniqueMachineLocation(ll); + if (!l.isPresent()) { + return Maybe.absent(); +// throw new IllegalStateException("Cannot find hostname for among "+ll); + } + return Machines.getSubnetHostname(l.get()); + } + + public static Maybe<String> findSubnetHostname(Entity entity) { + String sh = entity.getAttribute(Attributes.SUBNET_HOSTNAME); + if (sh!=null) return Maybe.of(sh); + return findSubnetHostname(entity.getLocations()); + } + + public static Maybe<String> findSubnetOrPublicHostname(Entity entity) { + String hn = entity.getAttribute(Attributes.HOSTNAME); + if (hn!=null) { + // attributes already set, see if there was a SUBNET_HOSTNAME set + // note we rely on (public) hostname being set _after_ subnet_hostname, + // to prevent tiny possibility of races resulting in hostname being returned + // becasue subnet is still being looked up -- see MachineLifecycleEffectorTasks + Maybe<String> sn = findSubnetHostname(entity); + if (sn.isPresent()) return sn; + // short-circuit discovery if attributes have been set already + return Maybe.of(hn); + } + + Maybe<MachineLocation> l = findUniqueMachineLocation(entity.getLocations()); + if (!l.isPresent()) return Maybe.absent(); + InetAddress addr = l.get().getAddress(); + if (addr==null) return Maybe.absent(); + return Maybe.fromNullable(addr.getHostName()); + } + + public static Maybe<String> findSubnetOrPrivateIp(Entity entity) { + // see comments in findSubnetOrPrivateHostname + String hn = entity.getAttribute(Attributes.ADDRESS); + if (hn!=null) { + Maybe<String> sn = findSubnetIp(entity); + if (sn.isPresent()) return sn; + return Maybe.of(hn); + } + + Maybe<MachineLocation> l = findUniqueMachineLocation(entity.getLocations()); + if (!l.isPresent()) return Maybe.absent(); + InetAddress addr = l.get().getAddress(); + if (addr==null) return Maybe.absent(); + return Maybe.fromNullable(addr.getHostAddress()); + } + + public static Maybe<String> findSubnetIp(Entity entity) { + String sh = entity.getAttribute(Attributes.SUBNET_ADDRESS); + if (sh!=null) return Maybe.of(sh); + return findSubnetIp(entity.getLocations()); + } + + public static Maybe<String> findSubnetIp(Iterable<? extends Location> ll) { + // TODO Or if can't find MachineLocation, should we throw new IllegalStateException("Cannot find hostname for among "+ll); + Maybe<MachineLocation> l = findUniqueMachineLocation(ll); + return (l.isPresent()) ? Machines.getSubnetIp(l.get()) : Maybe.<String>absent(); + } + + /** returns whether it is localhost (and has warned) */ + public static boolean warnIfLocalhost(Collection<? extends Location> locations, String message) { + if (locations.size()==1) { + Location l = locations.iterator().next(); + if (l instanceof LocalhostMachineProvisioningLocation || l instanceof LocalhostMachine) { + log.warn(message); + 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/MultiLocation.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/core/MultiLocation.java b/core/src/main/java/org/apache/brooklyn/location/core/MultiLocation.java new file mode 100644 index 0000000..9f52244 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/core/MultiLocation.java @@ -0,0 +1,166 @@ +/* + * 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 static com.google.common.base.Preconditions.checkState; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +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.MachineProvisioningLocation; +import org.apache.brooklyn.api.location.NoMachinesAvailableException; +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.location.cloud.AbstractAvailabilityZoneExtension; +import org.apache.brooklyn.location.cloud.AvailabilityZoneExtension; +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.core.flags.SetFromFlag; +import org.apache.brooklyn.util.exceptions.CompoundRuntimeException; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.text.Strings; + +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; +import com.google.common.reflect.TypeToken; + +/** A location which consists of multiple locations stitched together to form availability zones. + * The first location will be used by default, but if an {@link AvailabilityZoneExtension}-aware entity + * is used, it may stripe across each of the locations. See notes at {@link AvailabilityZoneExtension}. */ +public class MultiLocation<T extends MachineLocation> extends AbstractLocation implements MachineProvisioningLocation<T> { + + private static final long serialVersionUID = 7993091317970457862L; + + @SuppressWarnings("serial") + @SetFromFlag("subLocations") + public static final ConfigKey<List<MachineProvisioningLocation<?>>> SUB_LOCATIONS = ConfigKeys.newConfigKey( + new TypeToken<List<MachineProvisioningLocation<?>>>() {}, + "subLocations", + "The sub-machines that this location can delegate to"); + + @Override + public void init() { + super.init(); + List<MachineProvisioningLocation<?>> subLocs = getSubLocations(); + checkState(subLocs.size() >= 1, "sub-locations must not be empty"); + AvailabilityZoneExtension azExtension = new AvailabilityZoneExtensionImpl(getManagementContext(), subLocs); + addExtension(AvailabilityZoneExtension.class, azExtension); + } + + public T obtain() throws NoMachinesAvailableException { + return obtain(MutableMap.of()); + } + + /** finds (creates) and returns a {@link MachineLocation}; + * this always tries the first sub-location, moving on the second and subsequent if the first throws {@link NoMachinesAvailableException}. + * (if you want striping across locations, see notes in {@link AvailabilityZoneExtension}.) */ + @SuppressWarnings("unchecked") + @Override + public T obtain(Map<?, ?> flags) throws NoMachinesAvailableException { + List<MachineProvisioningLocation<?>> sublocsList = getSubLocations(); + Iterator<MachineProvisioningLocation<?>> sublocs = sublocsList.iterator(); + List<NoMachinesAvailableException> errors = MutableList.of(); + while (sublocs.hasNext()) { + try { + return (T) sublocs.next().obtain(flags); + } catch (NoMachinesAvailableException e) { + errors.add(e); + } + } + Exception wrapped; + String msg; + if (errors.size()>1) { + wrapped = new CompoundRuntimeException(errors.size()+" sublocation exceptions, including: "+ + Exceptions.collapseText(errors.get(0)), errors); + msg = Exceptions.collapseText(wrapped); + } else if (errors.size()==1) { + wrapped = errors.get(0); + msg = wrapped.getMessage(); + if (Strings.isBlank(msg)) msg = wrapped.toString(); + } else { + msg = "no sub-locations set for this multi-location"; + wrapped = null; + } + throw new NoMachinesAvailableException("No machines available in any of the "+sublocsList.size()+" location"+Strings.s(sublocsList.size())+ + " configured here: "+msg, wrapped); + } + + public List<MachineProvisioningLocation<?>> getSubLocations() { + return getRequiredConfig(SUB_LOCATIONS); + } + + @SuppressWarnings("unchecked") + @Override + public MachineProvisioningLocation<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)); + } + + @SuppressWarnings("unchecked") + @Override + public void release(T machine) { + ((MachineProvisioningLocation<T>)machine.getParent()).release(machine); + } + + @Override + public Map<String, Object> getProvisioningFlags(Collection<String> tags) { + return Maps.<String,Object>newLinkedHashMap(); + } + + @SuppressWarnings("unchecked") + protected MachineProvisioningLocation<T> firstSubLoc() { + return (MachineProvisioningLocation<T>) Iterables.get(getSubLocations(), 0); + } + + protected <K> K getRequiredConfig(ConfigKey<K> key) { + return checkNotNull(getConfig(key), key.getName()); + } + + public static class AvailabilityZoneExtensionImpl extends AbstractAvailabilityZoneExtension implements AvailabilityZoneExtension { + + private final List<MachineProvisioningLocation<?>> subLocations; + + public AvailabilityZoneExtensionImpl(ManagementContext managementContext, List<MachineProvisioningLocation<?>> subLocations) { + super(managementContext); + this.subLocations = ImmutableList.copyOf(subLocations); + } + + @Override + protected List<Location> doGetAllSubLocations() { + return ImmutableList.<Location>copyOf(subLocations); + } + + @Override + protected boolean isNameMatch(Location loc, Predicate<? super String> namePredicate) { + return namePredicate.apply(loc.getDisplayName()); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/core/MultiLocationResolver.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/core/MultiLocationResolver.java b/core/src/main/java/org/apache/brooklyn/location/core/MultiLocationResolver.java new file mode 100644 index 0000000..e20661f --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/core/MultiLocationResolver.java @@ -0,0 +1,145 @@ +/* + * 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.List; +import java.util.Map; +import java.util.Set; +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.util.collections.MutableMap; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.text.KeyValueParser; +import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.Lists; + +public class MultiLocationResolver implements LocationResolver { + + private static final Logger LOG = LoggerFactory.getLogger(MultiLocationResolver.class); + + private static final String MULTI = "multi"; + + private static final Pattern PATTERN = Pattern.compile("(" + MULTI + "|" + MULTI.toUpperCase() + ")" + ":" + "\\((.*)\\)$"); + + private volatile ManagementContext managementContext; + + @Override + public void init(ManagementContext managementContext) { + this.managementContext = checkNotNull(managementContext, "managementContext"); + } + + @Override + public Location newLocationFromString(Map locationFlags, String spec, LocationRegistry registry) { + // FIXME pass all flags into the location + + Map globalProperties = registry.getProperties(); + Map<String,?> locationArgs; + if (spec.equalsIgnoreCase(MULTI)) { + locationArgs = MutableMap.copyOf(locationFlags); + } else { + Matcher matcher = PATTERN.matcher(spec); + if (!matcher.matches()) { + throw new IllegalArgumentException("Invalid location '" + spec + "'; must specify something like multi(targets=named:foo)"); + } + String args = matcher.group(2); + // TODO we are ignoring locationFlags after this (apart from named), looking only at these args + locationArgs = KeyValueParser.parseMap(args); + } + String namedLocation = (String) locationFlags.get("named"); + + Map<String, Object> filteredProperties = new LocationPropertiesFromBrooklynProperties().getLocationProperties(null, namedLocation, globalProperties); + MutableMap<String, Object> flags = MutableMap.<String, Object>builder() + .putAll(filteredProperties) + .putAll(locationFlags) + .removeAll("named") + .putAll(locationArgs).build(); + + if (locationArgs.get("targets") == null) { + throw new IllegalArgumentException("target must be specified in single-machine spec"); + } + + // TODO do we need to pass location flags etc into the children to ensure they are inherited? + List<Location> targets = Lists.newArrayList(); + Object targetSpecs = locationArgs.remove("targets"); + try { + if (targetSpecs instanceof String) { + for (String targetSpec : JavaStringEscapes.unwrapJsonishListIfPossible((String)targetSpecs)) { + targets.add(managementContext.getLocationRegistry().resolve(targetSpec)); + } + } else if (targetSpecs instanceof Iterable) { + for (Object targetSpec: (Iterable<?>)targetSpecs) { + if (targetSpec instanceof String) { + targets.add(managementContext.getLocationRegistry().resolve((String)targetSpec)); + } else { + Set<?> keys = ((Map<?,?>)targetSpec).keySet(); + if (keys.size()!=1) + throw new IllegalArgumentException("targets supplied to MultiLocation must be a list of single-entry maps (got map of size "+keys.size()+": "+targetSpec+")"); + Object key = keys.iterator().next(); + Object flagsS = ((Map<?,?>)targetSpec).get(key); + targets.add(managementContext.getLocationRegistry().resolve((String)key, (Map<?,?>)flagsS)); + } + } + } else throw new IllegalArgumentException("targets must be supplied to MultiLocation, either as string spec or list of single-entry maps each being a location spec"); + + MultiLocation result = managementContext.getLocationManager().createLocation(LocationSpec.create(MultiLocation.class) + .configure(flags) + .configure("subLocations", targets) + .configure(LocationConfigUtils.finalAndOriginalSpecs(spec, locationFlags, globalProperties, namedLocation))); + + // TODO Important workaround for BasicLocationRegistry.resolveForPeeking. + // That creates a location (from the resolver) and immediately unmanages it. + // The unmanage *must* remove all the targets created here; otherwise we have a leak. + // Adding the targets as children achieves this. + for (Location target : targets) { + target.setParent(result); + } + return result; + + } catch (Exception e) { + // Must clean up after ourselves: don't leak sub-locations on error + if (LOG.isDebugEnabled()) LOG.debug("Problem resolving MultiLocation; cleaning up any sub-locations and rethrowing: "+e); + for (Location target : targets) { + Locations.unmanage(target); + } + throw Exceptions.propagate(e); + } + } + + @Override + public String getPrefix() { + return MULTI; + } + + @Override + public boolean accepts(String spec, LocationRegistry registry) { + return BasicLocationRegistry.isResolverPrefixForSpec(this, spec, true); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/core/NamedLocationResolver.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/core/NamedLocationResolver.java b/core/src/main/java/org/apache/brooklyn/location/core/NamedLocationResolver.java new file mode 100644 index 0000000..08976de --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/core/NamedLocationResolver.java @@ -0,0 +1,97 @@ +/* + * 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 java.util.NoSuchElementException; + +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.apache.brooklyn.location.core.internal.LocationInternal; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.text.Strings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Allows you to say, in your brooklyn.properties: + * + * brooklyn.location.named.foo=localhost + * brooklyn.location.named.foo.user=bob + * brooklyn.location.named.foo.privateKeyFile=~/.ssh/custom-key-for-bob + * brooklyn.location.named.foo.privateKeyPassphrase=WithAPassphrase + * <p> + * or + * <p> + * brooklyn.location.named.bob-aws-east=jclouds:aws-ec2:us-east-1 + * brooklyn.location.named.bob-aws-east.identity=BobId + * brooklyn.location.named.bob-aws-east.credential=BobCred + * <p> + * then you can simply refer to: foo or named:foo (or bob-aws-east or named:bob-aws-east) in any location spec + */ +public class NamedLocationResolver implements LocationResolver { + + public static final Logger log = LoggerFactory.getLogger(NamedLocationResolver.class); + + public static final String NAMED = "named"; + + @SuppressWarnings("unused") + private ManagementContext managementContext; + + @Override + public void init(ManagementContext managementContext) { + this.managementContext = checkNotNull(managementContext, "managementContext"); + } + + @Override + @SuppressWarnings({ "rawtypes" }) + public Location newLocationFromString(Map locationFlags, String spec, LocationRegistry registry) { + String name = spec; + ConfigBag lfBag = ConfigBag.newInstance(locationFlags).putIfAbsent(LocationInternal.ORIGINAL_SPEC, name); + name = Strings.removeFromStart(spec, getPrefix()+":"); + if (name.toLowerCase().startsWith(NAMED+":")) { + // since 0.7.0 + log.warn("Deprecated use of 'named:' prefix with wrong case ("+spec+"); support may be removed in future versions"); + name = spec.substring( (NAMED+":").length() ); + } + + LocationDefinition ld = registry.getDefinedLocationByName(name); + if (ld==null) throw new NoSuchElementException("No named location defined matching '"+name+"'"); + return ((BasicLocationRegistry)registry).resolveLocationDefinition(ld, lfBag.getAllConfig(), name); + } + + @Override + public String getPrefix() { + return NAMED; + } + + /** accepts anything starting named:xxx or xxx where xxx is a defined location name */ + @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/PortRanges.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/core/PortRanges.java b/core/src/main/java/org/apache/brooklyn/location/core/PortRanges.java new file mode 100644 index 0000000..72346f1 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/core/PortRanges.java @@ -0,0 +1,257 @@ +/* + * 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 java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.brooklyn.api.location.PortRange; +import org.apache.brooklyn.util.core.flags.TypeCoercions; + +import com.google.common.base.Function; +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; + +public class PortRanges { + + public static final int MAX_PORT = 65535; + public static final PortRange ANY_HIGH_PORT = new LinearPortRange(1024, MAX_PORT); + + public static class SinglePort implements PortRange, Serializable { + private static final long serialVersionUID = 7446781416534230401L; + + final int port; + private SinglePort(int port) { this.port = port; } + + @Override + public Iterator<Integer> iterator() { + return Collections.singletonList(port).iterator(); + } + @Override + public boolean isEmpty() { + return false; + } + @Override + public boolean asBoolean() { + return true; + } + @Override + public String toString() { + return //getClass().getName()+"["+ + ""+port; //+"]"; + } + public int hashCode() { + return Objects.hashCode(port); + } + @Override + public boolean equals(Object obj) { + return (obj instanceof SinglePort) && port == ((SinglePort)obj).port; + } + } + + public static class LinearPortRange implements PortRange, Serializable { + private static final long serialVersionUID = -9165280509363743508L; + + final int start, end, delta; + private LinearPortRange(int start, int end, int delta) { + this.start = start; + this.end = end; + this.delta = delta; + checkArgument(start > 0 && start <= MAX_PORT, "start port %s out of range", start); + checkArgument(end > 0 && end <= MAX_PORT, "end port %s out of range", end); + checkArgument(delta > 0 ? start <= end : start >= end, "start and end out of order: %s to %s, delta %s", start, end, delta); + checkArgument(delta != 0, "delta must be non-zero"); + } + public LinearPortRange(int start, int end) { + this(start, end, (start<=end?1:-1)); + } + + @Override + public Iterator<Integer> iterator() { + return new Iterator<Integer>() { + int next = start; + boolean hasNext = true; + + @Override + public boolean hasNext() { + return hasNext; + } + + @Override + public Integer next() { + if (!hasNext) + throw new NoSuchElementException("Exhausted available ports"); + int result = next; + next += delta; + if ((delta>0 && next>end) || (delta<0 && next<end)) hasNext = false; + return result; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Override + public boolean isEmpty() { + return false; + } + @Override + public boolean asBoolean() { + return true; + } + @Override + public String toString() { + return //getClass().getName()+"["+ + start+"-"+end; //+"]"; + } + @Override + public int hashCode() { + return Objects.hashCode(start, end, delta); + } + @Override + public boolean equals(Object obj) { + if (!(obj instanceof LinearPortRange)) return false; + LinearPortRange o = (LinearPortRange) obj; + return start == o.start && end == o.end && delta == o.delta; + } + } + + public static class AggregatePortRange implements PortRange, Serializable { + private static final long serialVersionUID = 7332682500816739660L; + + final List<PortRange> ranges; + private AggregatePortRange(List<PortRange> ranges) { + this.ranges = ImmutableList.copyOf(ranges); + } + @Override + public Iterator<Integer> iterator() { + return Iterables.concat(ranges).iterator(); + } + @Override + public boolean isEmpty() { + for (PortRange r: ranges) + if (!r.isEmpty()) return false; + return true; + } + @Override + public boolean asBoolean() { + return !isEmpty(); + } + @Override + public String toString() { + String s = ""; + for (PortRange r: ranges) { + if (s.length()>0) s+=","; + s += r; + } + return //getClass().getName()+"["+ + s; //+"]"; + } + public int hashCode() { + return Objects.hashCode(ranges); + } + @Override + public boolean equals(Object obj) { + return (obj instanceof AggregatePortRange) && ranges.equals(((AggregatePortRange)obj).ranges); + } + } + + public static PortRange fromInteger(int x) { + return new SinglePort(x); + } + + public static PortRange fromCollection(Collection<?> c) { + List<PortRange> l = new ArrayList<PortRange>(); + for (Object o: c) { + if (o instanceof Integer) l.add(fromInteger((Integer)o)); + else if (o instanceof String) l.add(fromString((String)o)); + else if (o instanceof Collection) l.add(fromCollection((Collection<?>)o)); + else l.add(TypeCoercions.coerce(o, PortRange.class)); + } + return new AggregatePortRange(l); + } + + /** parses a string representation of ports, as "80,8080,8000,8080-8099" */ + public static PortRange fromString(String s) { + List<PortRange> l = new ArrayList<PortRange>(); + for (String si: s.split(",")) { + si = si.trim(); + int start, end; + if (si.endsWith("+")) { + String si2 = si.substring(0, si.length()-1).trim(); + start = Integer.parseInt(si2); + end = MAX_PORT; + } else if (si.indexOf('-')>0) { + int v = si.indexOf('-'); + start = Integer.parseInt(si.substring(0, v).trim()); + end = Integer.parseInt(si.substring(v+1).trim()); + } else if (si.length()==0) { + //nothing, ie empty range, just continue + continue; + } else { + //should be number on its own + l.add(new SinglePort(Integer.parseInt(si))); + continue; + } + l.add(new LinearPortRange(start, end)); + } + if (l.size() == 1) { + return l.get(0); + } else { + return new AggregatePortRange(l); + } + } + + private static AtomicBoolean initialized = new AtomicBoolean(false); + /** performs the language extensions required for this project */ + @SuppressWarnings("rawtypes") + public static void init() { + if (initialized.get()) return; + synchronized (initialized) { + if (initialized.get()) return; + TypeCoercions.registerAdapter(Integer.class, PortRange.class, new Function<Integer,PortRange>() { + public PortRange apply(Integer x) { return fromInteger(x); } + }); + TypeCoercions.registerAdapter(String.class, PortRange.class, new Function<String,PortRange>() { + public PortRange apply(String x) { return fromString(x); } + }); + TypeCoercions.registerAdapter(Collection.class, PortRange.class, new Function<Collection,PortRange>() { + public PortRange apply(Collection x) { return fromCollection(x); } + }); + initialized.set(true); + } + } + + static { + init(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/core/RegistryLocationResolver.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/core/RegistryLocationResolver.java b/core/src/main/java/org/apache/brooklyn/location/core/RegistryLocationResolver.java new file mode 100644 index 0000000..9ac62d7 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/core/RegistryLocationResolver.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.location.core; + +import java.util.Map; + +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.location.LocationRegistry; +import org.apache.brooklyn.api.location.LocationResolver; + +/** + * Extension to LocationResolver which can take a registry. + * + * @deprecated since 0.6; the LocationResolver always takes the LocationRegistry now + */ +@Deprecated +public interface RegistryLocationResolver extends LocationResolver { + + @Override + @SuppressWarnings("rawtypes") + Location newLocationFromString(Map locationFlags, String spec, LocationRegistry registry); + + @Override + boolean accepts(String spec, LocationRegistry registry); + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/core/SupportsPortForwarding.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/core/SupportsPortForwarding.java b/core/src/main/java/org/apache/brooklyn/location/core/SupportsPortForwarding.java new file mode 100644 index 0000000..d0af5a4 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/core/SupportsPortForwarding.java @@ -0,0 +1,39 @@ +/* + * 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 org.apache.brooklyn.util.net.Cidr; + +import com.google.common.net.HostAndPort; + +public interface SupportsPortForwarding { + + /** returns an endpoint suitable for contacting the indicated private port on this object, + * from the given Cidr, creating it if necessary and possible; + * may return null if forwarding not available + */ + public HostAndPort getSocketEndpointFor(Cidr accessor, int privatePort); + + /** marker on a location to indicate that port forwarding should be done automatically + * for attempts to access from Brooklyn + */ + public interface RequiresPortForwarding extends SupportsPortForwarding { + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/core/internal/LocationDynamicType.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/core/internal/LocationDynamicType.java b/core/src/main/java/org/apache/brooklyn/location/core/internal/LocationDynamicType.java new file mode 100644 index 0000000..83316ec --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/core/internal/LocationDynamicType.java @@ -0,0 +1,40 @@ +/* + * 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.internal; + +import org.apache.brooklyn.core.objs.BrooklynDynamicType; +import org.apache.brooklyn.location.core.AbstractLocation; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.location.LocationType; + +public class LocationDynamicType extends BrooklynDynamicType<Location, AbstractLocation> { + + public LocationDynamicType(AbstractLocation location) { + super(location); + } + + public LocationType getSnapshot() { + return (LocationType) super.getSnapshot(); + } + + @Override + protected LocationTypeSnapshot newSnapshot() { + return new LocationTypeSnapshot(name, value(configKeys)); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/core/internal/LocationInternal.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/core/internal/LocationInternal.java b/core/src/main/java/org/apache/brooklyn/location/core/internal/LocationInternal.java new file mode 100644 index 0000000..9ec449b --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/core/internal/LocationInternal.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.core.internal; + +import java.util.Map; + +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.api.mgmt.rebind.RebindSupport; +import org.apache.brooklyn.api.mgmt.rebind.mementos.LocationMemento; +import org.apache.brooklyn.config.ConfigInheritance; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.objs.BrooklynObjectInternal; +import org.apache.brooklyn.util.core.config.ConfigBag; + +import com.google.common.annotations.Beta; + +/** + * Information about locations private to Brooklyn. + */ +public interface LocationInternal extends BrooklynObjectInternal, Location { + + @Beta + public static final ConfigKey<String> ORIGINAL_SPEC = ConfigKeys.newStringConfigKey("spec.original", "The original spec used to instantiate a location"); + @Beta + public static final ConfigKey<String> FINAL_SPEC = ConfigKeys.newStringConfigKey("spec.final", "The actual spec (in a chain) which instantiates a location"); + @Beta + public static final ConfigKey<String> NAMED_SPEC_NAME = ConfigKeys.newStringConfigKey("spec.named.name", "The name on the (first) named spec in a chain"); + + /** + * Registers the given extension for the given type. If an extension already existed for + * this type, then this will override it. + * + * @throws NullPointerException if extensionType or extension are null + * @throws IllegalArgumentException if extension does not implement extensionType + */ + <T> void addExtension(Class<T> extensionType, T extension); + + /** + * Get a record of the metadata of this location. + * <p/> + * <p>Metadata records are used to record an audit trail of events relating to location usage + * (for billing purposes, for example). Implementations (and subclasses) should override this + * method to return information useful for this purpose.</p> + * + * @return + */ + public Map<String, String> toMetadataRecord(); + + /** + * @deprecated since 0.7.0; use {@link #config()}, such as {@code ((LocationInternal)location).config().getLocalBag()} + */ + @Deprecated + ConfigBag getLocalConfigBag(); + + /** + * Returns all config, including that inherited from parents. + * + * This method does not respect {@link ConfigInheritance} and so usage is discouraged. + * + * @deprecated since 0.7.0; use {@link #config()}, such as {@code ((LocationInternal)location).config().getBag()} + */ + @Deprecated + ConfigBag getAllConfigBag(); + + /** + * Users are strongly discouraged from calling or overriding this method. + * It is for internal calls only, relating to persisting/rebinding entities. + * This method may change (or be removed) in a future release without notice. + */ + @Override + @Beta + RebindSupport<LocationMemento> getRebindSupport(); + + ManagementContext getManagementContext(); +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/core/internal/LocationTypeSnapshot.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/core/internal/LocationTypeSnapshot.java b/core/src/main/java/org/apache/brooklyn/location/core/internal/LocationTypeSnapshot.java new file mode 100644 index 0000000..b272b72 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/core/internal/LocationTypeSnapshot.java @@ -0,0 +1,40 @@ +/* + * 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.internal; + +import java.util.Map; + +import org.apache.brooklyn.api.sensor.EnricherType; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.objs.BrooklynTypeSnapshot; + +public class LocationTypeSnapshot extends BrooklynTypeSnapshot implements EnricherType { + + private static final long serialVersionUID = 9150132836104748237L; + + LocationTypeSnapshot(String name, Map<String, ConfigKey<?>> configKeys) { + super(name, configKeys); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + return (obj instanceof LocationTypeSnapshot) && super.equals(obj); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/geo/HostGeoInfo.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/geo/HostGeoInfo.java b/core/src/main/java/org/apache/brooklyn/location/geo/HostGeoInfo.java index dde47a6..fc88cd2 100644 --- a/core/src/main/java/org/apache/brooklyn/location/geo/HostGeoInfo.java +++ b/core/src/main/java/org/apache/brooklyn/location/geo/HostGeoInfo.java @@ -26,8 +26,8 @@ import javax.annotation.Nullable; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.location.AddressableLocation; import org.apache.brooklyn.api.location.Location; -import org.apache.brooklyn.location.basic.AbstractLocation; -import org.apache.brooklyn.location.basic.LocationConfigKeys; +import org.apache.brooklyn.location.core.AbstractLocation; +import org.apache.brooklyn.location.core.LocationConfigKeys; import org.apache.brooklyn.util.core.flags.TypeCoercions; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/localhost/LocalhostLocationResolver.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/localhost/LocalhostLocationResolver.java b/core/src/main/java/org/apache/brooklyn/location/localhost/LocalhostLocationResolver.java new file mode 100644 index 0000000..37d885d --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/localhost/LocalhostLocationResolver.java @@ -0,0 +1,76 @@ +/* + * 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.localhost; + +import java.util.Map; + +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.location.core.AbstractLocationResolver; +import org.apache.brooklyn.location.core.LocationConfigUtils; +import org.apache.brooklyn.util.core.config.ConfigBag; + +/** + * Examples of valid specs: + * <ul> + * <li>localhost + * <li>localhost() + * <li>localhost(name=abc) + * <li>localhost(name="abc") + * </ul> + * + * @author alex, aled + */ +public class LocalhostLocationResolver extends AbstractLocationResolver implements LocationResolver.EnableableLocationResolver { + + public static final String LOCALHOST = "localhost"; + + @Override + public String getPrefix() { + return LOCALHOST; + } + + @Override + public boolean isEnabled() { + return LocationConfigUtils.isEnabled(managementContext, "brooklyn.location.localhost"); + } + + @Override + protected Class<? extends Location> getLocationType() { + return LocalhostMachineProvisioningLocation.class; + } + + @Override + protected SpecParser getSpecParser() { + return new AbstractLocationResolver.SpecParser(getPrefix()).setExampleUsage("\"localhost\" or \"localhost(displayName=abc)\""); + } + + @Override + protected Map<String, Object> getFilteredLocationProperties(String provider, String namedLocation, Map<String, ?> globalProperties) { + return new LocalhostPropertiesFromBrooklynProperties().getLocationProperties("localhost", namedLocation, globalProperties); + } + + @Override + protected ConfigBag extractConfig(Map<?,?> locationFlags, String spec, LocationRegistry registry) { + ConfigBag config = super.extractConfig(locationFlags, spec, registry); + config.putAsStringKeyIfAbsent("name", "localhost"); + return config; + } +}
