http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/538324e1/core/src/main/java/org/apache/brooklyn/core/location/LocationPredicates.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/location/LocationPredicates.java b/core/src/main/java/org/apache/brooklyn/core/location/LocationPredicates.java new file mode 100644 index 0000000..fc336ec --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/location/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.core.location; + +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/538324e1/core/src/main/java/org/apache/brooklyn/core/location/LocationPropertiesFromBrooklynProperties.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/location/LocationPropertiesFromBrooklynProperties.java b/core/src/main/java/org/apache/brooklyn/core/location/LocationPropertiesFromBrooklynProperties.java new file mode 100644 index 0000000..c6ada78 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/location/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.core.location; + +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/538324e1/core/src/main/java/org/apache/brooklyn/core/location/Locations.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/location/Locations.java b/core/src/main/java/org/apache/brooklyn/core/location/Locations.java new file mode 100644 index 0000000..2cae5d7 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/location/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.core.location; + +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.core.entity.Entities; +import org.apache.brooklyn.core.location.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/538324e1/core/src/main/java/org/apache/brooklyn/core/location/Machines.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/location/Machines.java b/core/src/main/java/org/apache/brooklyn/core/location/Machines.java new file mode 100644 index 0000000..b95d42f --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/location/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.core.location; + +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.core.entity.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/538324e1/core/src/main/java/org/apache/brooklyn/core/location/NamedLocationResolver.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/location/NamedLocationResolver.java b/core/src/main/java/org/apache/brooklyn/core/location/NamedLocationResolver.java new file mode 100644 index 0000000..6dbe4d7 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/location/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.core.location; + +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.core.location.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/538324e1/core/src/main/java/org/apache/brooklyn/core/location/PortRanges.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/location/PortRanges.java b/core/src/main/java/org/apache/brooklyn/core/location/PortRanges.java new file mode 100644 index 0000000..07daba5 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/location/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.core.location; + +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/538324e1/core/src/main/java/org/apache/brooklyn/core/location/RegistryLocationResolver.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/location/RegistryLocationResolver.java b/core/src/main/java/org/apache/brooklyn/core/location/RegistryLocationResolver.java new file mode 100644 index 0000000..a2cf5fa --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/location/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.core.location; + +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/538324e1/core/src/main/java/org/apache/brooklyn/core/location/SupportsPortForwarding.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/location/SupportsPortForwarding.java b/core/src/main/java/org/apache/brooklyn/core/location/SupportsPortForwarding.java new file mode 100644 index 0000000..3510230 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/location/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.core.location; + +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/538324e1/core/src/main/java/org/apache/brooklyn/core/location/access/BrooklynAccessUtils.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/location/access/BrooklynAccessUtils.java b/core/src/main/java/org/apache/brooklyn/core/location/access/BrooklynAccessUtils.java new file mode 100644 index 0000000..1ac354f --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/location/access/BrooklynAccessUtils.java @@ -0,0 +1,154 @@ +/* + * 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.core.location.access; + +import java.util.Collection; + +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.config.ConfigKey; +import org.apache.brooklyn.core.config.BasicConfigKey; +import org.apache.brooklyn.core.entity.Attributes; +import org.apache.brooklyn.core.location.Machines; +import org.apache.brooklyn.core.location.SupportsPortForwarding; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.brooklyn.location.ssh.SshMachineLocation; +import org.apache.brooklyn.util.core.task.DynamicTasks; +import org.apache.brooklyn.util.core.task.Tasks; +import org.apache.brooklyn.util.core.task.ssh.SshTasks; +import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.net.Cidr; +import org.apache.brooklyn.util.text.Strings; +import org.python.google.common.base.Predicates; +import org.python.google.common.collect.Iterables; + +import com.google.common.base.Supplier; +import com.google.common.net.HostAndPort; + +public class BrooklynAccessUtils { + + private static final Logger log = LoggerFactory.getLogger(BrooklynAccessUtils.class); + + public static final ConfigKey<PortForwardManager> PORT_FORWARDING_MANAGER = new BasicConfigKey<PortForwardManager>( + PortForwardManager.class, "brooklyn.portforwarding.manager", "A port-forwarding manager to use at an entity " + + "or a location, where supported; note this should normally be a serializable client instance to prevent " + + "the creation of multiple disconnected instances via config duplication"); + + public static final ConfigKey<Cidr> MANAGEMENT_ACCESS_CIDR = new BasicConfigKey<Cidr>( + Cidr.class, "brooklyn.portforwarding.management.cidr", "CIDR to enable by default for port-forwarding for management", + null); // TODO should be a list + + public static HostAndPort getBrooklynAccessibleAddress(Entity entity, int port) { + String host; + + // look up port forwarding + PortForwardManager pfw = entity.getConfig(PORT_FORWARDING_MANAGER); + if (pfw!=null) { + Collection<Location> ll = entity.getLocations(); + + synchronized (BrooklynAccessUtils.class) { + // TODO finer-grained synchronization + + for (MachineLocation machine : Iterables.filter(ll, MachineLocation.class)) { + HostAndPort hp = pfw.lookup(machine, port); + if (hp!=null) { + log.debug("BrooklynAccessUtils found port-forwarded address {} for entity {}, port {}, using machine {}", + new Object[] {hp, entity, port, machine}); + return hp; + } + } + + Maybe<SupportsPortForwarding> supportPortForwardingLoc = Machines.findUniqueElement(ll, SupportsPortForwarding.class); + if (supportPortForwardingLoc.isPresent()) { + Cidr source = entity.getConfig(MANAGEMENT_ACCESS_CIDR); + SupportsPortForwarding loc = supportPortForwardingLoc.get(); + if (source!=null) { + log.debug("BrooklynAccessUtils requesting new port-forwarding rule to access "+port+" on "+entity+" (at "+loc+", enabled for "+source+")"); + // TODO discuss, is this the best way to do it + // (will probably _create_ the port forwarding rule!) + HostAndPort hp = loc.getSocketEndpointFor(source, port); + if (hp!=null) { + log.debug("BrooklynAccessUtils created port-forwarded address {} for entity {}, port {}, using {}", + new Object[] {hp, entity, port, loc}); + return hp; + } + } else { + log.warn("No "+MANAGEMENT_ACCESS_CIDR.getName()+" configured for "+entity+", so cannot forward " + +"port "+port+" "+"even though "+PORT_FORWARDING_MANAGER.getName()+" was supplied, and " + +"have location supporting port forwarding "+loc); + } + } + } + } + + host = entity.getAttribute(Attributes.HOSTNAME); + if (host!=null) return HostAndPort.fromParts(host, port); + + throw new IllegalStateException("Cannot find way to access port "+port+" on "+entity+" from Brooklyn (no host.name)"); + } + + /** attempts to resolve hostnameTarget from origin + * @return null if it definitively can't be resolved, + * best-effort IP address if possible, or blank if we could not run ssh or make sense of the output */ + public static String getResolvedAddress(Entity entity, SshMachineLocation origin, String hostnameTarget) { + ProcessTaskWrapper<Integer> task = SshTasks.newSshExecTaskFactory(origin, "ping -c 1 -t 1 "+hostnameTarget) + .summary("checking resolution of "+hostnameTarget).allowingNonZeroExitCode().newTask(); + DynamicTasks.queueIfPossible(task).orSubmitAndBlock(entity).asTask().blockUntilEnded(); + if (task.asTask().isError()) { + log.warn("ping could not be run, at "+entity+" / "+origin+": "+Tasks.getError(task.asTask())); + return ""; + } + if (task.getExitCode()==null || task.getExitCode()!=0) { + if (task.getExitCode()!=null && task.getExitCode()<10) { + // small number means ping failed to resolve or ping the hostname + log.debug("not able to resolve "+hostnameTarget+" from "+origin+" for "+entity+" because exit code was "+task.getExitCode()); + return null; + } + // large number means ping probably did not run + log.warn("ping not run as expected, at "+entity+" / "+origin+" (code "+task.getExitCode()+"):\n"+task.getStdout().trim()+" --- "+task.getStderr().trim()); + return ""; + } + String out = task.getStdout(); + try { + String line1 = Strings.getFirstLine(out); + String ip = Strings.getFragmentBetween(line1, "(", ")"); + if (Strings.isNonBlank(ip)) + return ip; + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + /* ignore non-parseable output */ + } + if (out.contains("127.0.0.1")) return "127.0.0.1"; + return ""; + } + + public static Supplier<String> resolvedAddressSupplier(final Entity entity, final SshMachineLocation origin, final String hostnameTarget) { + return new Supplier<String>() { + @Override + public String get() { + return getResolvedAddress(entity, origin, hostnameTarget); + } + }; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/538324e1/core/src/main/java/org/apache/brooklyn/core/location/access/PortForwardManager.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/location/access/PortForwardManager.java b/core/src/main/java/org/apache/brooklyn/core/location/access/PortForwardManager.java new file mode 100644 index 0000000..21e579c --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/location/access/PortForwardManager.java @@ -0,0 +1,328 @@ +/* + * 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.core.location.access; + +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; + +import com.google.common.annotations.Beta; +import com.google.common.base.Objects; +import com.google.common.base.Predicate; +import com.google.common.net.HostAndPort; + +import java.util.Collection; + +/** + * Acts as a registry for existing port mappings (e.g. the public endpoints for accessing specific + * ports on private VMs). This could be using DNAT, or iptables port-forwarding, or Docker port-mapping + * via the host, or any other port mapping approach. + * + * Also controls the allocation of ports via {@link #acquirePublicPort(String)} + * (e.g. for port-mapping with DNAT, then which port to use for the public side). + * + * Implementations typically will not know anything about what the firewall/IP actually is, they just + * handle a unique identifier for it. + * + * To use, see {@link PortForwardManagerLocationResolver}, with code such as + * {@code managementContext.getLocationRegistry().resolve("portForwardManager(scope=global)")}. + * + * @see PortForwardManagerImpl for implementation notes and considerations. + */ +@Beta +public interface PortForwardManager extends Location { + + @Beta + class AssociationMetadata { + private final String publicIpId; + private final HostAndPort publicEndpoint; + private final Location location; + private final int privatePort; + + /** + * Users are discouraged from calling this constructor; the signature may change in future releases. + * Instead, instances will be created automatically by Brooklyn to be passed to the + * {@link AssociationListener#onAssociationCreated(AssociationMetadata)} method. + */ + public AssociationMetadata(String publicIpId, HostAndPort publicEndpoint, Location location, int privatePort) { + this.publicIpId = publicIpId; + this.publicEndpoint = publicEndpoint; + this.location = location; + this.privatePort = privatePort; + } + + public String getPublicIpId() { + return publicIpId; + } + + public HostAndPort getPublicEndpoint() { + return publicEndpoint; + } + + public Location getLocation() { + return location; + } + + public int getPrivatePort() { + return privatePort; + } + + public String toString() { + return Objects.toStringHelper(this) + .add("publicIpId", publicIpId) + .add("publicEndpoint", publicEndpoint) + .add("location", location) + .add("privatePort", privatePort) + .toString(); + } + } + + @Beta + interface AssociationListener { + void onAssociationCreated(AssociationMetadata metadata); + void onAssociationDeleted(AssociationMetadata metadata); + } + + /** + * The intention is that there is one PortForwardManager instance per "scope". If you + * use global, then it will be a shared instance (for that management context). If you + * pass in your own name (e.g. "docker-fjie3") then it will shared with just any other + * places that use that same location spec (e.g. {@code portForwardManager(scope=docker-fjie3)}). + */ + // TODO Note: using name "scope" rather than "brooklyn.portForwardManager.scope" so that location spec + // "portForwardManager(scope=global)" works, rather than having to do + // portForwardManager(brooklyn.portForwardManager.scope=global). + // The config being read by the PortForwardManagerLocationResolver doesn't respect @SetFromFlag("scope"). + public static final ConfigKey<String> SCOPE = ConfigKeys.newStringConfigKey( + "scope", + "The scope that this applies to, defaulting to global", + "global"); + + @Beta + public static final ConfigKey<Integer> PORT_FORWARD_MANAGER_STARTING_PORT = ConfigKeys.newIntegerConfigKey( + "brooklyn.portForwardManager.startingPort", + "The starting port for assigning port numbers, such as for DNAT", + 11000); + + public String getScope(); + + /** + * Reserves a unique public port on the given publicIpId. + * <p> + * Often followed by {@link #associate(String, HostAndPort, int)} or {@link #associate(String, HostAndPort, Location, int)} + * to enable {@link #lookup(String, int)} or {@link #lookup(Location, int)} respectively. + */ + public int acquirePublicPort(String publicIpId); + + /** + * Records a location and private port against a public endpoint (ip and port), + * to support {@link #lookup(Location, int)}. + * <p> + * Superfluous if {@link #acquirePublicPort(String, Location, int)} was used, + * but strongly recommended if {@link #acquirePublicPortExplicit(String, int)} was used + * e.g. if the location is not known ahead of time. + */ + public void associate(String publicIpId, HostAndPort publicEndpoint, Location l, int privatePort); + + /** + * Records a mapping for publicIpId:privatePort to a public endpoint, such that it can + * subsequently be looked up using {@link #lookup(String, int)}. + */ + public void associate(String publicIpId, HostAndPort publicEndpoint, int privatePort); + + /** + * Registers a listener, which will be notified each time a new port mapping is associated. See {@link #associate(String, HostAndPort, int)} + * and {@link #associate(String, HostAndPort, Location, int)}. + */ + @Beta + public void addAssociationListener(AssociationListener listener, Predicate<? super AssociationMetadata> filter); + + @Beta + public void removeAssociationListener(AssociationListener listener); + + /** + * Returns the public ip hostname and public port for use contacting the given endpoint. + * <p> + * Will return null if: + * <ul> + * <li>No publicPort is associated with this location and private port. + * <li>No publicIpId is associated with this location and private port. + * <li>No publicIpHostname is recorded against the associated publicIpId. + * </ul> + * Conceivably this may have to be access-location specific. + * + * @see #recordPublicIpHostname(String, String) + */ + public HostAndPort lookup(Location l, int privatePort); + + /** + * Returns the public endpoint (host and port) for use contacting the given endpoint. + * + * Expects a previous call to {@link #associate(String, HostAndPort, int)}, to register + * the endpoint. + * + * Will return null if there has not been a public endpoint associated with this pairing. + */ + public HostAndPort lookup(String publicIpId, int privatePort); + + /** + * Clears the given port mapping, returning true if there was a match. + */ + public boolean forgetPortMapping(String publicIpId, int publicPort); + + /** + * Clears the port mappings associated with the given location, returning true if there were any matches. + */ + public boolean forgetPortMappings(Location location); + + /** + * Clears the port mappings associated with the given publicIpId, returning true if there were any matches. + */ + public boolean forgetPortMappings(String publicIpId); + + public String toVerboseString(); + + + /////////////////////////////////////////////////////////////////////////////////// + // Deprecated + /////////////////////////////////////////////////////////////////////////////////// + + /** + * Reserves a unique public port for the purpose of forwarding to the given target, + * associated with a given location for subsequent lookup purpose. + * <p> + * If already allocated, returns the previously allocated. + * + * @deprecated since 0.7.0; use {@link #acquirePublicPort(String)}, and then use {@link #associate(String, HostAndPort, int)} or {@link #associate(String, HostAndPort, Location, int)} + */ + @Deprecated + public int acquirePublicPort(String publicIpId, Location l, int privatePort); + + /** + * Returns old mapping if it existed, null if it is new. + * + * @deprecated since 0.7.0; use {@link #associate(String, HostAndPort, int)} or {@link #associate(String, HostAndPort, Location, int)} + */ + @Deprecated + public PortMapping acquirePublicPortExplicit(String publicIpId, int port); + + /** + * Records a location and private port against a publicIp and public port, + * to support {@link #lookup(Location, int)}. + * <p> + * Superfluous if {@link #acquirePublicPort(String, Location, int)} was used, + * but strongly recommended if {@link #acquirePublicPortExplicit(String, int)} was used + * e.g. if the location is not known ahead of time. + * + * @deprecated Use {@link #associate(String, HostAndPort, Location, int)} + */ + @Deprecated + public void associate(String publicIpId, int publicPort, Location l, int privatePort); + + /** + * Records a public hostname or address to be associated with the given publicIpId for lookup purposes. + * <p> + * Conceivably this may have to be access-location specific. + * + * @deprecated Use {@link #associate(String, HostAndPort, int)} or {@link #associate(String, HostAndPort, Location, int)} + */ + @Deprecated + public void recordPublicIpHostname(String publicIpId, String hostnameOrPublicIpAddress); + + /** + * Returns a recorded public hostname or address. + * + * @deprecated Use {@link #lookup(String, int)} or {@link #lookup(Location, int)} + */ + @Deprecated + public String getPublicIpHostname(String publicIpId); + + /** + * Clears a previous call to {@link #recordPublicIpHostname(String, String)}. + * + * @deprecated Use {@link #forgetPortMapping(String, int)} or {@link #forgetPortMappings(Location)} + */ + @Deprecated + public boolean forgetPublicIpHostname(String publicIpId); + + /** + * Returns true if this implementation is a client which is immutable/safe for serialization + * i.e. it delegates to something on an entity or location elsewhere. + * + * @deprecated since 0.7.0; no need to separate client-proxy from impl + */ + @Deprecated + public boolean isClient(); + + + /////////////////////////////////////////////////////////////////////////////////// + // Deprecated; just internal + /////////////////////////////////////////////////////////////////////////////////// + + /** + * Returns the port mapping for a given publicIpId and public port. + * + * @deprecated since 0.7.0; this method will be internal only + */ + @Deprecated + public PortMapping getPortMappingWithPublicSide(String publicIpId, int publicPort); + + /** + * Returns the subset of port mappings associated with a given public IP ID. + * + * @deprecated since 0.7.0; this method will be internal only + */ + @Deprecated + public Collection<PortMapping> getPortMappingWithPublicIpId(String publicIpId); + + /** + * @see {@link #forgetPortMapping(String, int)} and {@link #forgetPortMappings(Location)} + * + * @deprecated since 0.7.0; this method will be internal only + */ + @Deprecated + public boolean forgetPortMapping(PortMapping m); + + /** + * Returns the public host and port for use accessing the given mapping. + * <p> + * Conceivably this may have to be access-location specific. + * + * @deprecated since 0.7.0; this method will be internal only + */ + @Deprecated + public HostAndPort getPublicHostAndPort(PortMapping m); + + /** + * Returns the subset of port mappings associated with a given location. + * + * @deprecated since 0.7.0; this method will be internal only + */ + @Deprecated + public Collection<PortMapping> getLocationPublicIpIds(Location l); + + /** + * Returns the mapping to a given private port, or null if none. + * + * @deprecated since 0.7.0; this method will be internal only + */ + @Deprecated + public PortMapping getPortMappingWithPrivateSide(Location l, int privatePort); + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/538324e1/core/src/main/java/org/apache/brooklyn/core/location/access/PortForwardManagerAuthority.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/location/access/PortForwardManagerAuthority.java b/core/src/main/java/org/apache/brooklyn/core/location/access/PortForwardManagerAuthority.java new file mode 100644 index 0000000..89c57f3 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/location/access/PortForwardManagerAuthority.java @@ -0,0 +1,46 @@ +/* + * 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.core.location.access; + + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.core.entity.EntityInternal; + +/** + * @deprecated since 0.7.0; use {@link PortForwardManagerImpl} + */ +@Deprecated +public class PortForwardManagerAuthority extends PortForwardManagerImpl { + private Entity owningEntity; + + public PortForwardManagerAuthority() { + } + + public PortForwardManagerAuthority(Entity owningEntity) { + this.owningEntity = owningEntity; + } + + protected void onChanged() { + if (owningEntity != null) { + ((EntityInternal) owningEntity).requestPersist(); + } else { + super.onChanged(); + } + } +}
