http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/538324e1/core/src/main/java/org/apache/brooklyn/core/location/access/PortForwardManagerClient.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/location/access/PortForwardManagerClient.java b/core/src/main/java/org/apache/brooklyn/core/location/access/PortForwardManagerClient.java new file mode 100644 index 0000000..12e15aa --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/location/access/PortForwardManagerClient.java @@ -0,0 +1,405 @@ +/* + * 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 java.util.Map; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.config.ConfigKey.HasConfigKey; +import org.apache.brooklyn.util.exceptions.Exceptions; + +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.base.Supplier; +import com.google.common.net.HostAndPort; + +/** + * @deprecated since 0.7.0; just use the {@link PortForwardManager}, or a direct reference to its impl {@link PortForwardManagerImpl} + */ +@Deprecated +public class PortForwardManagerClient implements PortForwardManager { + + private static final long serialVersionUID = -295204304305332895L; + + protected final Supplier<PortForwardManager> delegateSupplier; + private transient volatile PortForwardManager _delegate; + + protected PortForwardManagerClient(Supplier<PortForwardManager> supplier) { + this.delegateSupplier = supplier; + } + + /** creates an instance given a supplier; + * the supplier should be brooklyn-persistable, that is to say + * references should be in terms of entities/locations + * which can retrieve an authoritative source even under cloning */ + public static PortForwardManager fromSupplier(Supplier<PortForwardManager> supplier) { + return new PortForwardManagerClient(supplier); + } + + /** creates an instance given an entity and an interface method it implements to retrieve the PortForwardManager */ + public static PortForwardManager fromMethodOnEntity(final Entity entity, final String getterMethodOnEntity) { + Preconditions.checkNotNull(entity); + Preconditions.checkNotNull(getterMethodOnEntity); + return new PortForwardManagerClient(new Supplier<PortForwardManager>() { + @Override + public PortForwardManager get() { + PortForwardManager result; + try { + result = (PortForwardManager) entity.getClass().getMethod(getterMethodOnEntity).invoke(entity); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + throw new IllegalStateException("Cannot invoke "+getterMethodOnEntity+" on "+entity+" ("+entity.getClass()+"): "+e, e); + } + if (result==null) + throw new IllegalStateException("No PortForwardManager available via "+getterMethodOnEntity+" on "+entity+" (returned null)"); + return result; + } + }); + } + + /** creates an instance given an entity and {@link AttributeSensor} to retrieve the PortForwardManager */ + public static PortForwardManager fromAttributeOnEntity(final Entity entity, final AttributeSensor<PortForwardManager> attributeOnEntity) { + Preconditions.checkNotNull(entity); + Preconditions.checkNotNull(attributeOnEntity); + return new PortForwardManagerClient(new Supplier<PortForwardManager>() { + @Override + public PortForwardManager get() { + PortForwardManager result = entity.getAttribute(attributeOnEntity); + if (result==null) + throw new IllegalStateException("No PortForwardManager available via "+attributeOnEntity+" on "+entity+" (returned null)"); + return result; + } + }); + } + + protected PortForwardManager getDelegate() { + if (_delegate==null) { + _delegate = delegateSupplier.get(); + } + return _delegate; + } + + @Override + public int acquirePublicPort(String publicIpId) { + return getDelegate().acquirePublicPort(publicIpId); + } + + @Override + public void associate(String publicIpId, HostAndPort publicEndpoint, Location l, int privatePort) { + getDelegate().associate(publicIpId, publicEndpoint, l, privatePort); + } + + @Override + public void associate(String publicIpId, HostAndPort publicEndpoint, int privatePort) { + getDelegate().associate(publicIpId, publicEndpoint, privatePort); + } + + @Override + public HostAndPort lookup(Location l, int privatePort) { + return getDelegate().lookup(l, privatePort); + } + + @Override + public HostAndPort lookup(String publicIpId, int privatePort) { + return getDelegate().lookup(publicIpId, privatePort); + } + + @Override + public boolean forgetPortMapping(String publicIpId, int publicPort) { + return getDelegate().forgetPortMapping(publicIpId, publicPort); + } + + @Override + public boolean forgetPortMappings(Location location) { + return getDelegate().forgetPortMappings(location); + } + + @Override + public boolean forgetPortMappings(String publicIpId) { + return getDelegate().forgetPortMappings(publicIpId); + } + + @Override + public String getId() { + return getDelegate().getId(); + } + + @Override + public String getScope() { + return getDelegate().getScope(); + } + + @Override + public void addAssociationListener(AssociationListener listener, Predicate<? super AssociationMetadata> filter) { + getDelegate().addAssociationListener(listener, filter); + } + + @Override + public void removeAssociationListener(AssociationListener listener) { + getDelegate().removeAssociationListener(listener); + } + + @Override + public String toVerboseString() { + return getClass().getName()+"[wrapping="+getDelegate().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)} + */ + @Override + @Deprecated + public int acquirePublicPort(String publicIpId, Location l, int privatePort) { + return getDelegate().acquirePublicPort(publicIpId, l, 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)} + */ + @Override + @Deprecated + public PortMapping acquirePublicPortExplicit(String publicIpId, int publicPort) { + return getDelegate().acquirePublicPortExplicit(publicIpId, publicPort); + } + + /** + * 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)} + */ + @Override + @Deprecated + public void associate(String publicIpId, int publicPort, Location l, int privatePort) { + getDelegate().associate(publicIpId, publicPort, l, 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)} + */ + @Override + @Deprecated + public void recordPublicIpHostname(String publicIpId, String hostnameOrPublicIpAddress) { + getDelegate().recordPublicIpHostname(publicIpId, hostnameOrPublicIpAddress); + } + + /** + * Returns a recorded public hostname or address. + * + * @deprecated Use {@link #lookup(String, int)} or {@link #lookup(Location, int)} + */ + @Override + @Deprecated + public String getPublicIpHostname(String publicIpId) { + return getDelegate().getPublicIpHostname(publicIpId); + } + + /** + * Clears a previous call to {@link #recordPublicIpHostname(String, String)}. + * + * @deprecated Use {@link #forgetPortMapping(String, int)} or {@link #forgetPortMapping(Location, int)} + */ + @Override + @Deprecated + public boolean forgetPublicIpHostname(String publicIpId) { + return getDelegate().forgetPublicIpHostname(publicIpId); + } + + @Override + @Deprecated + public boolean isClient() { + return true; + } + + + /////////////////////////////////////////////////////////////////////////////////// + // 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 + */ + @Override + @Deprecated + public PortMapping getPortMappingWithPublicSide(String publicIpId, int publicPort) { + return getDelegate().getPortMappingWithPublicSide(publicIpId, 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 + */ + @Override + @Deprecated + public Collection<PortMapping> getPortMappingWithPublicIpId(String publicIpId) { + return getDelegate().getPortMappingWithPublicIpId(publicIpId); + } + + /** + * @see #forgetPortMapping(String, int) + * + * @deprecated since 0.7.0; this method will be internal only + */ + @Override + @Deprecated + public boolean forgetPortMapping(PortMapping m) { + return getDelegate().forgetPortMapping(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 + */ + @Override + @Deprecated + public HostAndPort getPublicHostAndPort(PortMapping m) { + return getDelegate().getPublicHostAndPort(m); + } + + /** + * Returns the subset of port mappings associated with a given location. + * + * @deprecated since 0.7.0; this method will be internal only + */ + @Override + @Deprecated + public Collection<PortMapping> getLocationPublicIpIds(Location l) { + return getDelegate().getLocationPublicIpIds(l); + } + + /** + * Returns the mapping to a given private port, or null if none. + * + * @deprecated since 0.7.0; this method will be internal only + */ + @Override + @Deprecated + public PortMapping getPortMappingWithPrivateSide(Location l, int privatePort) { + return getDelegate().getPortMappingWithPrivateSide(l, privatePort); + } + + @Override + public String toString() { + return getClass().getName()+"[id="+getId()+"]"; + } + + @Override + public String getDisplayName() { + return getDelegate().getDisplayName(); + } + + @Override + public Location getParent() { + return getDelegate().getParent(); + } + + @Override + public Collection<Location> getChildren() { + return getDelegate().getChildren(); + } + + @Override + public void setParent(Location newParent) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean containsLocation(Location potentialDescendent) { + return getDelegate().containsLocation(potentialDescendent); + } + + @Override + public <T> T getConfig(ConfigKey<T> key) { + return getDelegate().getConfig(key); + } + + @Override + public <T> T getConfig(HasConfigKey<T> key) { + return getDelegate().getConfig(key); + } + + @Override + public boolean hasConfig(ConfigKey<?> key, boolean includeInherited) { + return getDelegate().hasConfig(key, includeInherited); + } + + @Override + public Map<String, Object> getAllConfig(boolean includeInherited) { + return getDelegate().getAllConfig(includeInherited); + } + + @Override + public boolean hasExtension(Class<?> extensionType) { + return getDelegate().hasExtension(extensionType); + } + + @Override + public <T> T getExtension(Class<T> extensionType) { + return getDelegate().getExtension(extensionType); + } + + @Override + public String getCatalogItemId() { + return getDelegate().getCatalogItemId(); + } + + @Override + public TagSupport tags() { + return getDelegate().tags(); + } + + @Override + public <T> T setConfig(ConfigKey<T> key, T val) { + return getDelegate().setConfig(key, val); + } + + @Override + public ConfigurationSupport config() { + return getDelegate().config(); + } +}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/538324e1/core/src/main/java/org/apache/brooklyn/core/location/access/PortForwardManagerImpl.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/location/access/PortForwardManagerImpl.java b/core/src/main/java/org/apache/brooklyn/core/location/access/PortForwardManagerImpl.java new file mode 100644 index 0000000..30be900 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/location/access/PortForwardManagerImpl.java @@ -0,0 +1,505 @@ +/* + * 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 static com.google.common.base.Preconditions.checkNotNull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.mgmt.rebind.RebindContext; +import org.apache.brooklyn.api.mgmt.rebind.RebindSupport; +import org.apache.brooklyn.api.mgmt.rebind.mementos.LocationMemento; +import org.apache.brooklyn.core.location.AbstractLocation; +import org.apache.brooklyn.core.mgmt.rebind.BasicLocationRebindSupport; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Objects.ToStringHelper; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.google.common.net.HostAndPort; + +/** + * + * @author aled + * + * TODO This implementation is not efficient, and currently has a cap of about 50000 rules. + * Need to improve the efficiency and scale. + * A quick win could be to use a different portReserved counter for each publicIpId, + * when calling acquirePublicPort? + * + * TODO Callers need to be more careful in acquirePublicPort for which ports are actually in use. + * If multiple apps sharing the same public-ip (e.g. in the same vcloud-director vOrg) then they + * must not allocate the same public port (e.g. ensure they share the same PortForwardManager + * by using the same scope in + * {@code managementContext.getLocationRegistry().resolve("portForwardManager(scope=global)")}. + * However, this still doesn't check if the port is *actually* available. For example, if a + * different Brooklyn instance is also deploying there then we can get port conflicts, or if + * some ports in that range are already in use (e.g. due to earlier dev/test runs) then this + * will not be respected. Callers should probably figure out the port number themselves, but + * that also leads to concurrency issues. + * + * TODO The publicIpId means different things to different callers: + * <ul> + * <li> In acquirePublicPort() it is (often?) an identifier of the actual public ip. + * <li> In later calls to associate(), it is (often?) an identifier for the target machine + * such as the jcloudsMachine.getJcloudsId(). + * </ul> + */ +@SuppressWarnings("serial") +public class PortForwardManagerImpl extends AbstractLocation implements PortForwardManager { + + private static final Logger log = LoggerFactory.getLogger(PortForwardManagerImpl.class); + + protected final Map<String,PortMapping> mappings = new LinkedHashMap<String,PortMapping>(); + + private final Map<AssociationListener, Predicate<? super AssociationMetadata>> associationListeners = new ConcurrentHashMap<AssociationListener, Predicate<? super AssociationMetadata>>(); + + @Deprecated + protected final Map<String,String> publicIpIdToHostname = new LinkedHashMap<String,String>(); + + // horrible hack -- see javadoc above + private final AtomicInteger portReserved = new AtomicInteger(11000); + + private final Object mutex = new Object(); + + public PortForwardManagerImpl() { + super(); + if (isLegacyConstruction()) { + log.warn("Deprecated construction of "+PortForwardManagerImpl.class.getName()+"; instead use location resolver"); + } + } + + @Override + public void init() { + super.init(); + Integer portStartingPoint; + Object rawPort = getAllConfigBag().getStringKey(PORT_FORWARD_MANAGER_STARTING_PORT.getName()); + if (rawPort != null) { + portStartingPoint = getConfig(PORT_FORWARD_MANAGER_STARTING_PORT); + } else { + portStartingPoint = getManagementContext().getConfig().getConfig(PORT_FORWARD_MANAGER_STARTING_PORT); + } + portReserved.set(portStartingPoint); + log.debug(this+" set initial port to "+portStartingPoint); + } + + // TODO Need to use attributes for these so they are persisted (once a location is an entity), + // rather than this deprecated approach of custom fields. + @Override + public RebindSupport<LocationMemento> getRebindSupport() { + return new BasicLocationRebindSupport(this) { + @Override public LocationMemento getMemento() { + Map<String, PortMapping> mappingsCopy; + Map<String,String> publicIpIdToHostnameCopy; + synchronized (mutex) { + mappingsCopy = MutableMap.copyOf(mappings); + publicIpIdToHostnameCopy = MutableMap.copyOf(publicIpIdToHostname); + } + return getMementoWithProperties(MutableMap.<String,Object>of( + "mappings", mappingsCopy, + "portReserved", portReserved.get(), + "publicIpIdToHostname", publicIpIdToHostnameCopy)); + } + @Override + protected void doReconstruct(RebindContext rebindContext, LocationMemento memento) { + super.doReconstruct(rebindContext, memento); + mappings.putAll( Preconditions.checkNotNull((Map<String, PortMapping>) memento.getCustomField("mappings"), "mappings was not serialized correctly")); + portReserved.set( (Integer)memento.getCustomField("portReserved")); + publicIpIdToHostname.putAll( Preconditions.checkNotNull((Map<String, String>)memento.getCustomField("publicIpIdToHostname"), "publicIpIdToHostname was not serialized correctly") ); + } + }; + } + + @Override + public int acquirePublicPort(String publicIpId) { + int port; + synchronized (mutex) { + // far too simple -- see javadoc above + port = getNextPort(); + + // TODO When delete deprecated code, stop registering PortMapping until associate() is called + PortMapping mapping = new PortMapping(publicIpId, port, null, -1); + log.debug(this+" allocating public port "+port+" on "+publicIpId+" (no association info yet)"); + + mappings.put(makeKey(publicIpId, port), mapping); + } + onChanged(); + return port; + } + + protected int getNextPort() { + // far too simple -- see javadoc above + return portReserved.getAndIncrement(); + } + + @Override + public void associate(String publicIpId, HostAndPort publicEndpoint, Location l, int privatePort) { + associateImpl(publicIpId, publicEndpoint, l, privatePort); + emitAssociationCreatedEvent(publicIpId, publicEndpoint, l, privatePort); + } + + @Override + public void associate(String publicIpId, HostAndPort publicEndpoint, int privatePort) { + associateImpl(publicIpId, publicEndpoint, null, privatePort); + emitAssociationCreatedEvent(publicIpId, publicEndpoint, null, privatePort); + } + + protected void associateImpl(String publicIpId, HostAndPort publicEndpoint, Location l, int privatePort) { + synchronized (mutex) { + String publicIp = publicEndpoint.getHostText(); + int publicPort = publicEndpoint.getPort(); + recordPublicIpHostname(publicIpId, publicIp); + PortMapping mapping = new PortMapping(publicIpId, publicEndpoint, l, privatePort); + PortMapping oldMapping = getPortMappingWithPublicSide(publicIpId, publicPort); + log.debug(this+" associating public "+publicEndpoint+" on "+publicIpId+" with private port "+privatePort+" at "+l+" ("+mapping+")" + +(oldMapping == null ? "" : " (overwriting "+oldMapping+" )")); + mappings.put(makeKey(publicIpId, publicPort), mapping); + } + onChanged(); + } + + private void emitAssociationCreatedEvent(String publicIpId, HostAndPort publicEndpoint, Location location, int privatePort) { + AssociationMetadata metadata = new AssociationMetadata(publicIpId, publicEndpoint, location, privatePort); + for (Map.Entry<AssociationListener, Predicate<? super AssociationMetadata>> entry : associationListeners.entrySet()) { + if (entry.getValue().apply(metadata)) { + try { + entry.getKey().onAssociationCreated(metadata); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + log.warn("Exception thrown when emitting association creation event " + metadata, e); + } + } + } + } + + @Override + public HostAndPort lookup(Location l, int privatePort) { + synchronized (mutex) { + for (PortMapping m: mappings.values()) { + if (l.equals(m.target) && privatePort == m.privatePort) + return getPublicHostAndPort(m); + } + } + return null; + } + + @Override + public HostAndPort lookup(String publicIpId, int privatePort) { + synchronized (mutex) { + for (PortMapping m: mappings.values()) { + if (publicIpId.equals(m.publicIpId) && privatePort==m.privatePort) + return getPublicHostAndPort(m); + } + } + return null; + } + + @Override + public boolean forgetPortMapping(String publicIpId, int publicPort) { + PortMapping old; + synchronized (mutex) { + old = mappings.remove(makeKey(publicIpId, publicPort)); + if (old != null) { + emitAssociationDeletedEvent(associationMetadataFromPortMapping(old)); + } + log.debug("cleared port mapping for "+publicIpId+":"+publicPort+" - "+old); + } + if (old != null) onChanged(); + return (old != null); + } + + @Override + public boolean forgetPortMappings(Location l) { + List<PortMapping> result = Lists.newArrayList(); + synchronized (mutex) { + for (Iterator<PortMapping> iter = mappings.values().iterator(); iter.hasNext();) { + PortMapping m = iter.next(); + if (l.equals(m.target)) { + iter.remove(); + result.add(m); + emitAssociationDeletedEvent(associationMetadataFromPortMapping(m)); + } + } + } + if (log.isDebugEnabled()) log.debug("cleared all port mappings for "+l+" - "+result); + if (!result.isEmpty()) { + onChanged(); + } + return !result.isEmpty(); + } + + @Override + public boolean forgetPortMappings(String publicIpId) { + List<PortMapping> result = Lists.newArrayList(); + synchronized (mutex) { + for (Iterator<PortMapping> iter = mappings.values().iterator(); iter.hasNext();) { + PortMapping m = iter.next(); + if (publicIpId.equals(m.publicIpId)) { + iter.remove(); + result.add(m); + emitAssociationDeletedEvent(associationMetadataFromPortMapping(m)); + } + } + } + if (log.isDebugEnabled()) log.debug("cleared all port mappings for "+publicIpId+" - "+result); + if (!result.isEmpty()) { + onChanged(); + } + return !result.isEmpty(); + } + + private void emitAssociationDeletedEvent(AssociationMetadata metadata) { + for (Map.Entry<AssociationListener, Predicate<? super AssociationMetadata>> entry : associationListeners.entrySet()) { + if (entry.getValue().apply(metadata)) { + try { + entry.getKey().onAssociationDeleted(metadata); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + log.warn("Exception thrown when emitting association creation event " + metadata, e); + } + } + } + } + + @Override + protected ToStringHelper string() { + int size; + synchronized (mutex) { + size = mappings.size(); + } + return super.string().add("scope", getScope()).add("mappingsSize", size); + } + + @Override + public String toVerboseString() { + String mappingsStr; + synchronized (mutex) { + mappingsStr = mappings.toString(); + } + return string().add("mappings", mappingsStr).toString(); + } + + @Override + public String getScope() { + return checkNotNull(getConfig(SCOPE), "scope"); + } + + @Override + public boolean isClient() { + return false; + } + + @Override + public void addAssociationListener(AssociationListener listener, Predicate<? super AssociationMetadata> filter) { + associationListeners.put(listener, filter); + } + + @Override + public void removeAssociationListener(AssociationListener listener) { + associationListeners.remove(listener); + } + + protected String makeKey(String publicIpId, int publicPort) { + return publicIpId+":"+publicPort; + } + + private AssociationMetadata associationMetadataFromPortMapping(PortMapping portMapping) { + String publicIpId = portMapping.getPublicEndpoint().getHostText(); + HostAndPort publicEndpoint = portMapping.getPublicEndpoint(); + Location location = portMapping.getTarget(); + int privatePort = portMapping.getPrivatePort(); + return new AssociationMetadata(publicIpId, publicEndpoint, location, privatePort); + } + + /////////////////////////////////////////////////////////////////////////////////// + // Internal state, for generating memento + /////////////////////////////////////////////////////////////////////////////////// + + public List<PortMapping> getPortMappings() { + synchronized (mutex) { + return ImmutableList.copyOf(mappings.values()); + } + } + + public Map<String, Integer> getPortCounters() { + return ImmutableMap.of("global", portReserved.get()); + } + + + /////////////////////////////////////////////////////////////////////////////////// + // Deprecated + /////////////////////////////////////////////////////////////////////////////////// + + @Override + @Deprecated + public PortMapping acquirePublicPortExplicit(String publicIpId, int port) { + PortMapping mapping = new PortMapping(publicIpId, port, null, -1); + log.debug("assigning explicit public port "+port+" at "+publicIpId); + PortMapping result; + synchronized (mutex) { + result = mappings.put(makeKey(publicIpId, port), mapping); + } + onChanged(); + return result; + } + + @Override + @Deprecated + public boolean forgetPortMapping(PortMapping m) { + return forgetPortMapping(m.publicIpId, m.publicPort); + } + + @Override + @Deprecated + public void recordPublicIpHostname(String publicIpId, String hostnameOrPublicIpAddress) { + log.debug("recording public IP "+publicIpId+" associated with "+hostnameOrPublicIpAddress); + synchronized (mutex) { + String old = publicIpIdToHostname.put(publicIpId, hostnameOrPublicIpAddress); + if (old!=null && !old.equals(hostnameOrPublicIpAddress)) + log.warn("Changing hostname recorded against public IP "+publicIpId+"; from "+old+" to "+hostnameOrPublicIpAddress); + } + onChanged(); + } + + @Override + @Deprecated + public String getPublicIpHostname(String publicIpId) { + synchronized (mutex) { + return publicIpIdToHostname.get(publicIpId); + } + } + + @Override + @Deprecated + public boolean forgetPublicIpHostname(String publicIpId) { + log.debug("forgetting public IP "+publicIpId+" association"); + boolean result; + synchronized (mutex) { + result = (publicIpIdToHostname.remove(publicIpId) != null); + } + onChanged(); + return result; + } + + @Override + @Deprecated + public int acquirePublicPort(String publicIpId, Location l, int privatePort) { + int publicPort; + synchronized (mutex) { + PortMapping old = getPortMappingWithPrivateSide(l, privatePort); + // only works for 1 public IP ID per location (which is the norm) + if (old!=null && old.publicIpId.equals(publicIpId)) { + log.debug("request to acquire public port at "+publicIpId+" for "+l+":"+privatePort+", reusing old assignment "+old); + return old.getPublicPort(); + } + + publicPort = acquirePublicPort(publicIpId); + log.debug("request to acquire public port at "+publicIpId+" for "+l+":"+privatePort+", allocating "+publicPort); + associateImpl(publicIpId, publicPort, l, privatePort); + } + onChanged(); + return publicPort; + } + + @Override + @Deprecated + public void associate(String publicIpId, int publicPort, Location l, int privatePort) { + synchronized (mutex) { + associateImpl(publicIpId, publicPort, l, privatePort); + } + onChanged(); + } + + protected void associateImpl(String publicIpId, int publicPort, Location l, int privatePort) { + synchronized (mutex) { + PortMapping mapping = new PortMapping(publicIpId, publicPort, l, privatePort); + PortMapping oldMapping = getPortMappingWithPublicSide(publicIpId, publicPort); + log.debug("associating public port "+publicPort+" on "+publicIpId+" with private port "+privatePort+" at "+l+" ("+mapping+")" + +(oldMapping == null ? "" : " (overwriting "+oldMapping+" )")); + mappings.put(makeKey(publicIpId, publicPort), mapping); + } + } + + /////////////////////////////////////////////////////////////////////////////////// + // Internal only; make protected when deprecated interface method removed + /////////////////////////////////////////////////////////////////////////////////// + + @Override + public HostAndPort getPublicHostAndPort(PortMapping m) { + if (m.publicEndpoint == null) { + String hostname = getPublicIpHostname(m.publicIpId); + if (hostname==null) + throw new IllegalStateException("No public hostname associated with "+m.publicIpId+" (mapping "+m+")"); + return HostAndPort.fromParts(hostname, m.publicPort); + } else { + return m.publicEndpoint; + } + } + + @Override + public PortMapping getPortMappingWithPublicSide(String publicIpId, int publicPort) { + synchronized (mutex) { + return mappings.get(makeKey(publicIpId, publicPort)); + } + } + + @Override + public Collection<PortMapping> getPortMappingWithPublicIpId(String publicIpId) { + List<PortMapping> result = new ArrayList<PortMapping>(); + synchronized (mutex) { + for (PortMapping m: mappings.values()) + if (publicIpId.equals(m.publicIpId)) result.add(m); + } + return result; + } + + /** returns the subset of port mappings associated with a given location */ + @Override + public Collection<PortMapping> getLocationPublicIpIds(Location l) { + List<PortMapping> result = new ArrayList<PortMapping>(); + synchronized (mutex) { + for (PortMapping m: mappings.values()) + if (l.equals(m.getTarget())) result.add(m); + } + return result; + } + + @Override + public PortMapping getPortMappingWithPrivateSide(Location l, int privatePort) { + synchronized (mutex) { + for (PortMapping m: mappings.values()) + if (l.equals(m.getTarget()) && privatePort==m.privatePort) return m; + } + return null; + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/538324e1/core/src/main/java/org/apache/brooklyn/core/location/access/PortForwardManagerLocationResolver.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/location/access/PortForwardManagerLocationResolver.java b/core/src/main/java/org/apache/brooklyn/core/location/access/PortForwardManagerLocationResolver.java new file mode 100644 index 0000000..3a52877 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/location/access/PortForwardManagerLocationResolver.java @@ -0,0 +1,89 @@ +/* + * 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.Map; + +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.location.LocationRegistry; +import org.apache.brooklyn.api.location.LocationSpec; +import org.apache.brooklyn.core.location.AbstractLocationResolver; +import org.apache.brooklyn.core.location.LocationConfigUtils; +import org.apache.brooklyn.core.location.LocationPredicates; +import org.apache.brooklyn.core.location.internal.LocationInternal; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.brooklyn.util.core.config.ConfigBag; + +import com.google.common.base.Optional; +import com.google.common.base.Predicates; +import com.google.common.collect.Iterables; + +public class PortForwardManagerLocationResolver extends AbstractLocationResolver { + + private static final Logger LOG = LoggerFactory.getLogger(PortForwardManagerLocationResolver.class); + + public static final String PREFIX = "portForwardManager"; + + @Override + public String getPrefix() { + return PREFIX; + } + + @Override + public Location newLocationFromString(Map locationFlags, String spec, LocationRegistry registry) { + ConfigBag config = extractConfig(locationFlags, spec, registry); + Map globalProperties = registry.getProperties(); + String namedLocation = (String) locationFlags.get(LocationInternal.NAMED_SPEC_NAME.getName()); + String scope = config.get(PortForwardManager.SCOPE); + + Optional<Location> result = Iterables.tryFind(managementContext.getLocationManager().getLocations(), + Predicates.and( + Predicates.instanceOf(PortForwardManager.class), + LocationPredicates.configEqualTo(PortForwardManager.SCOPE, scope))); + + if (result.isPresent()) { + return result.get(); + } else { + PortForwardManager loc = managementContext.getLocationManager().createLocation(LocationSpec.create(PortForwardManagerImpl.class) + .configure(config.getAllConfig()) + .configure(LocationConfigUtils.finalAndOriginalSpecs(spec, locationFlags, globalProperties, namedLocation))); + + if (LOG.isDebugEnabled()) LOG.debug("Created "+loc+" for scope "+scope); + return loc; + } + } + + @Override + protected Class<? extends Location> getLocationType() { + return PortForwardManager.class; + } + + @Override + protected SpecParser getSpecParser() { + return new AbstractLocationResolver.SpecParser(getPrefix()).setExampleUsage("\"portForwardManager\" or \"portForwardManager(scope=global)\""); + } + + @Override + protected ConfigBag extractConfig(Map<?,?> locationFlags, String spec, LocationRegistry registry) { + ConfigBag config = super.extractConfig(locationFlags, spec, registry); + config.putAsStringKeyIfAbsent("name", "localhost"); + return config; + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/538324e1/core/src/main/java/org/apache/brooklyn/core/location/access/PortMapping.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/location/access/PortMapping.java b/core/src/main/java/org/apache/brooklyn/core/location/access/PortMapping.java new file mode 100644 index 0000000..06d92cb --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/location/access/PortMapping.java @@ -0,0 +1,101 @@ +/* + * 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 static com.google.common.base.Preconditions.checkNotNull; + +import javax.annotation.Nullable; + +import org.apache.brooklyn.api.location.Location; + +import com.google.common.annotations.Beta; +import com.google.common.base.Objects; +import com.google.common.net.HostAndPort; + +public class PortMapping { + + final String publicIpId; + final HostAndPort publicEndpoint; + final int publicPort; + + final Location target; + final int privatePort; + // TODO CIDR's ? + + public PortMapping(String publicIpId, HostAndPort publicEndpoint, Location target, int privatePort) { + this.publicIpId = checkNotNull(publicIpId, "publicIpId"); + this.publicEndpoint = checkNotNull(publicEndpoint, "publicEndpoint"); + this.publicPort = publicEndpoint.getPort(); + this.target = target; + this.privatePort = privatePort; + } + + public PortMapping(String publicIpId, int publicPort, Location target, int privatePort) { + this.publicIpId = checkNotNull(publicIpId, "publicIpId"); + this.publicEndpoint = null; + this.publicPort = publicPort; + this.target = target; + this.privatePort = privatePort; + } + + // In a release after 0.7.0, this will no longer be @Nullable + @Beta + @Nullable + public HostAndPort getPublicEndpoint() { + return publicEndpoint; + } + + public int getPublicPort() { + return publicPort; + } + + public Location getTarget() { + return target; + } + + public int getPrivatePort() { + return privatePort; + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("publicIpId", publicIpId+":"+publicPort) + .add("publicEndpoint", (publicEndpoint == null ? publicPort : publicEndpoint)) + .add("targetLocation", target) + .add("targetPort", privatePort) + .toString(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof PortMapping)) return false; + PortMapping opm = (PortMapping)obj; + return Objects.equal(publicIpId, opm.publicIpId) && + Objects.equal(publicPort, opm.publicPort) && + Objects.equal(target, opm.target) && + Objects.equal(privatePort, opm.privatePort); + } + + @Override + public int hashCode() { + return Objects.hashCode(publicIpId, publicPort, target, privatePort); + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/538324e1/core/src/main/java/org/apache/brooklyn/core/location/cloud/AbstractAvailabilityZoneExtension.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/location/cloud/AbstractAvailabilityZoneExtension.java b/core/src/main/java/org/apache/brooklyn/core/location/cloud/AbstractAvailabilityZoneExtension.java new file mode 100644 index 0000000..267f708 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/location/cloud/AbstractAvailabilityZoneExtension.java @@ -0,0 +1,82 @@ +/* + * 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.cloud; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.mgmt.ManagementContext; + +import com.google.common.annotations.Beta; +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +@Beta +public abstract class AbstractAvailabilityZoneExtension implements AvailabilityZoneExtension { + + protected final ManagementContext managementContext; + protected final AtomicReference<List<Location>> subLocations = new AtomicReference<List<Location>>(); + private final Object mutex = new Object(); + + public AbstractAvailabilityZoneExtension(ManagementContext managementContext) { + this.managementContext = checkNotNull(managementContext, "managementContext"); + } + + @Override + public List<Location> getSubLocations(int max) { + List<Location> all = getAllSubLocations(); + return all.subList(0, Math.min(max, all.size())); + } + + @Override + public List<Location> getSubLocationsByName(Predicate<? super String> namePredicate, int max) { + List<Location> result = Lists.newArrayList(); + List<Location> all = getAllSubLocations(); + for (Location loc : all) { + if (isNameMatch(loc, namePredicate)) { + result.add(loc); + } + } + return Collections.<Location>unmodifiableList(result); + } + + @Override + public List<Location> getAllSubLocations() { + synchronized (mutex) { + if (subLocations.get() == null) { + List<Location> result = doGetAllSubLocations(); + subLocations.set(ImmutableList.copyOf(result)); + } + } + return subLocations.get(); + } + + /** + * <strong>Note</strong> this method can be called while synchronized on {@link #mutex}. + */ + // TODO bad pattern, as this will likely call alien code (such as asking cloud provider?!) + protected abstract List<Location> doGetAllSubLocations(); + + protected abstract boolean isNameMatch(Location loc, Predicate<? super String> namePredicate); +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/538324e1/core/src/main/java/org/apache/brooklyn/core/location/cloud/AbstractCloudMachineProvisioningLocation.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/location/cloud/AbstractCloudMachineProvisioningLocation.java b/core/src/main/java/org/apache/brooklyn/core/location/cloud/AbstractCloudMachineProvisioningLocation.java new file mode 100644 index 0000000..504033a --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/location/cloud/AbstractCloudMachineProvisioningLocation.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.cloud; + +import java.util.Collection; +import java.util.Map; + +import org.apache.brooklyn.api.location.LocationSpec; +import org.apache.brooklyn.api.location.MachineLocation; +import org.apache.brooklyn.api.location.MachineProvisioningLocation; +import org.apache.brooklyn.core.location.AbstractLocation; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.core.internal.ssh.SshTool; + +public abstract class AbstractCloudMachineProvisioningLocation extends AbstractLocation +implements MachineProvisioningLocation<MachineLocation>, CloudLocationConfig +{ + public AbstractCloudMachineProvisioningLocation() { + super(); + } + + /** typically wants at least ACCESS_IDENTITY and ACCESS_CREDENTIAL */ + public AbstractCloudMachineProvisioningLocation(Map<?,?> conf) { + super(conf); + } + + /** uses reflection to create an object of the same type, assuming a Map constructor; + * subclasses can extend and downcast the result */ + @Override + public AbstractCloudMachineProvisioningLocation newSubLocation(Map<?,?> newFlags) { + return newSubLocation(getClass(), newFlags); + } + + public AbstractCloudMachineProvisioningLocation newSubLocation(Class<? extends AbstractCloudMachineProvisioningLocation> type, Map<?,?> newFlags) { + // TODO should be able to use ConfigBag.newInstanceExtending; would require moving stuff around to api etc + // TODO was previously `return LocationCreationUtils.newSubLocation(newFlags, this)`; need to retest on CloudStack etc + return getManagementContext().getLocationManager().createLocation(LocationSpec.create(type) + .parent(this) + .configure(config().getLocalBag().getAllConfig()) // FIXME Should this just be inherited? + .configure(newFlags)); + } + + @Override + public Map<String, Object> getProvisioningFlags(Collection<String> tags) { + if (tags.size() > 0) { + LOG.warn("Location {}, ignoring provisioning tags {}", this, tags); + } + return MutableMap.<String, Object>of(); + } + + // ---------------- utilities -------------------- + + protected ConfigBag extractSshConfig(ConfigBag setup, ConfigBag alt) { + ConfigBag sshConfig = new ConfigBag(); + + if (setup.containsKey(PASSWORD)) { + sshConfig.put(SshTool.PROP_PASSWORD, setup.get(PASSWORD)); + } else if (alt.containsKey(PASSWORD)) { + sshConfig.put(SshTool.PROP_PASSWORD, alt.get(PASSWORD)); + } + + if (setup.containsKey(PRIVATE_KEY_DATA)) { + sshConfig.put(SshTool.PROP_PRIVATE_KEY_DATA, setup.get(PRIVATE_KEY_DATA)); + } else if (setup.containsKey(PRIVATE_KEY_FILE)) { + sshConfig.put(SshTool.PROP_PRIVATE_KEY_FILE, setup.get(PRIVATE_KEY_FILE)); + } else if (alt.containsKey(PRIVATE_KEY_DATA)) { + sshConfig.put(SshTool.PROP_PRIVATE_KEY_DATA, alt.get(PRIVATE_KEY_DATA)); + } + + if (setup.containsKey(PRIVATE_KEY_PASSPHRASE)) { + // NB: not supported in jclouds (but it is by our ssh tool) + sshConfig.put(SshTool.PROP_PRIVATE_KEY_PASSPHRASE, setup.get(PRIVATE_KEY_PASSPHRASE)); + } + + // TODO extract other SshTool properties ? + + return sshConfig; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/538324e1/core/src/main/java/org/apache/brooklyn/core/location/cloud/AvailabilityZoneExtension.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/location/cloud/AvailabilityZoneExtension.java b/core/src/main/java/org/apache/brooklyn/core/location/cloud/AvailabilityZoneExtension.java new file mode 100644 index 0000000..657438d --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/location/cloud/AvailabilityZoneExtension.java @@ -0,0 +1,54 @@ +/* + * 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.cloud; + +import java.util.List; + +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.entity.group.DynamicCluster; +import org.apache.brooklyn.location.multi.MultiLocation; + +import com.google.common.annotations.Beta; +import com.google.common.base.Predicate; + +/** + * For a location that has sub-zones within it (e.g. an AWS region has availability zones that can be + * mapped as sub-locations), this extension interface allows those to be accessed and used. + * For some well-known clouds, the availability zones are automatically set, although for others they may + * have to be configured explicitly. The "multi:(locs,...)" location descriptor (cf {@link MultiLocation}) allows + * this to be down at runtime. + * <p> + * Note that only entities which are explicitly aware of the {@link AvailabilityZoneExtension} + * will use availability zone information. For example {@link DynamicCluster} + * <p> + * Implementers are strongly encouraged to extend {@link AbstractAvailabilityZoneExtension} + * which has useful behaviour, rather than attempt to implement this interface directly. + * + * @since 0.6.0 + */ +@Beta +public interface AvailabilityZoneExtension { + + List<Location> getAllSubLocations(); + + List<Location> getSubLocations(int max); + + List<Location> getSubLocationsByName(Predicate<? super String> namePredicate, int max); + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/538324e1/core/src/main/java/org/apache/brooklyn/core/location/cloud/CloudLocationConfig.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/location/cloud/CloudLocationConfig.java b/core/src/main/java/org/apache/brooklyn/core/location/cloud/CloudLocationConfig.java new file mode 100644 index 0000000..37989f0 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/location/cloud/CloudLocationConfig.java @@ -0,0 +1,116 @@ +/* + * 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.cloud; + +import java.util.Collection; + +import com.google.common.annotations.Beta; +import com.google.common.reflect.TypeToken; + +import org.apache.brooklyn.api.location.MachineLocationCustomizer; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.BasicConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.location.LocationConfigKeys; +import org.apache.brooklyn.util.core.flags.SetFromFlag; + +public interface CloudLocationConfig { + + public static final ConfigKey<String> CLOUD_ENDPOINT = LocationConfigKeys.CLOUD_ENDPOINT; + public static final ConfigKey<String> CLOUD_REGION_ID = LocationConfigKeys.CLOUD_REGION_ID; + public static final ConfigKey<String> CLOUD_AVAILABILITY_ZONE_ID = LocationConfigKeys.CLOUD_AVAILABILITY_ZONE_ID; + + @SetFromFlag("identity") + public static final ConfigKey<String> ACCESS_IDENTITY = LocationConfigKeys.ACCESS_IDENTITY; + @SetFromFlag("credential") + public static final ConfigKey<String> ACCESS_CREDENTIAL = LocationConfigKeys.ACCESS_CREDENTIAL; + + public static final ConfigKey<String> USER = LocationConfigKeys.USER; + + public static final ConfigKey<String> PASSWORD = LocationConfigKeys.PASSWORD; + public static final ConfigKey<String> PUBLIC_KEY_FILE = LocationConfigKeys.PUBLIC_KEY_FILE; + public static final ConfigKey<String> PUBLIC_KEY_DATA = LocationConfigKeys.PUBLIC_KEY_DATA; + public static final ConfigKey<String> PRIVATE_KEY_FILE = LocationConfigKeys.PRIVATE_KEY_FILE; + public static final ConfigKey<String> PRIVATE_KEY_DATA = LocationConfigKeys.PRIVATE_KEY_DATA; + public static final ConfigKey<String> PRIVATE_KEY_PASSPHRASE = LocationConfigKeys.PRIVATE_KEY_PASSPHRASE; + + /** @deprecated since 0.6.0; included here so it gets picked up in auto-detect routines */ @Deprecated + public static final ConfigKey<String> LEGACY_PUBLIC_KEY_FILE = LocationConfigKeys.LEGACY_PUBLIC_KEY_FILE; + /** @deprecated since 0.6.0; included here so it gets picked up in auto-detect routines */ @Deprecated + public static final ConfigKey<String> LEGACY_PUBLIC_KEY_DATA = LocationConfigKeys.LEGACY_PUBLIC_KEY_DATA; + /** @deprecated since 0.6.0; included here so it gets picked up in auto-detect routines */ @Deprecated + public static final ConfigKey<String> LEGACY_PRIVATE_KEY_FILE = LocationConfigKeys.LEGACY_PRIVATE_KEY_FILE; + /** @deprecated since 0.6.0; included here so it gets picked up in auto-detect routines */ @Deprecated + public static final ConfigKey<String> LEGACY_PRIVATE_KEY_DATA = LocationConfigKeys.LEGACY_PRIVATE_KEY_DATA; + /** @deprecated since 0.6.0; included here so it gets picked up in auto-detect routines */ @Deprecated + public static final ConfigKey<String> LEGACY_PRIVATE_KEY_PASSPHRASE = LocationConfigKeys.LEGACY_PRIVATE_KEY_PASSPHRASE; + + // default is just shy of common 64-char boundary, leaving 4 chars plus our salt allowance (default 4+1) which allows up to -12345678 by jclouds + public static final ConfigKey<Integer> VM_NAME_MAX_LENGTH = ConfigKeys.newIntegerConfigKey( + "vmNameMaxLength", "Maximum length of VM name", 60); + + public static final ConfigKey<Integer> VM_NAME_SALT_LENGTH = ConfigKeys.newIntegerConfigKey( + "vmNameSaltLength", "Number of characters to use for a random identifier inserted in hostname " + + "to uniquely identify machines", 4); + + public static final ConfigKey<String> WAIT_FOR_SSHABLE = ConfigKeys.newStringConfigKey("waitForSshable", + "Whether and how long to wait for a newly provisioned VM to be accessible via ssh; " + + "if 'false', won't check; if 'true' uses default duration; otherwise accepts a time string e.g. '5m' (the default) or a number of milliseconds", "5m"); + + public static final ConfigKey<String> WAIT_FOR_WINRM_AVAILABLE = ConfigKeys.newStringConfigKey("waitForWinRmAvailable", + "Whether and how long to wait for a newly provisioned VM to be accessible via WinRm; " + + "if 'false', won't check; if 'true' uses default duration; otherwise accepts a time string e.g. '30m' (the default) or a number of milliseconds", "30m"); + + public static final ConfigKey<Boolean> LOG_CREDENTIALS = ConfigKeys.newBooleanConfigKey( + "logCredentials", + "Whether to log credentials of a new VM - strongly recommended never be used in production, as it is a big security hole!", + false); + + public static final ConfigKey<Object> CALLER_CONTEXT = LocationConfigKeys.CALLER_CONTEXT; + + public static final ConfigKey<Boolean> DESTROY_ON_FAILURE = ConfigKeys.newBooleanConfigKey("destroyOnFailure", "Whether to destroy the VM if provisioningLocation.obtain() fails", true); + + public static final ConfigKey<Object> INBOUND_PORTS = new BasicConfigKey<Object>(Object.class, "inboundPorts", + "Inbound ports to be applied when creating a VM, on supported clouds " + + "(either a single port as a String, or an Iterable<Integer> or Integer[])", null); + @Beta + public static final ConfigKey<Object> ADDITIONAL_INBOUND_PORTS = new BasicConfigKey<Object>(Object.class, "required.ports", + "Required additional ports to be applied when creating a VM, on supported clouds " + + "(either a single port as an Integer, or an Iterable<Integer> or Integer[])", null); + + public static final ConfigKey<Boolean> OS_64_BIT = ConfigKeys.newBooleanConfigKey("os64Bit", + "Whether to require 64-bit OS images (true), 32-bit images (false), or either (null)"); + + public static final ConfigKey<Object> MIN_RAM = new BasicConfigKey<Object>(Object.class, "minRam", + "Minimum amount of RAM, either as string (4gb) or number of MB (4096), for use in selecting the machine/hardware profile", null); + + public static final ConfigKey<Integer> MIN_CORES = new BasicConfigKey<Integer>(Integer.class, "minCores", + "Minimum number of cores, for use in selecting the machine/hardware profile", null); + + public static final ConfigKey<Object> MIN_DISK = new BasicConfigKey<Object>(Object.class, "minDisk", + "Minimum size of disk, either as string (100gb) or number of GB (100), for use in selecting the machine/hardware profile", null); + + public static final ConfigKey<String> DOMAIN_NAME = new BasicConfigKey<String>(String.class, "domainName", + "DNS domain where the host should be created, e.g. yourdomain.com (selected clouds only)", null); + + @SuppressWarnings("serial") + public static final ConfigKey<Collection<MachineLocationCustomizer>> MACHINE_LOCATION_CUSTOMIZERS = ConfigKeys.newConfigKey( + new TypeToken<Collection<MachineLocationCustomizer>>() {}, + "machineCustomizers", "Optional machine customizers"); +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/538324e1/core/src/main/java/org/apache/brooklyn/core/location/cloud/names/AbstractCloudMachineNamer.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/location/cloud/names/AbstractCloudMachineNamer.java b/core/src/main/java/org/apache/brooklyn/core/location/cloud/names/AbstractCloudMachineNamer.java new file mode 100644 index 0000000..7f38964 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/location/cloud/names/AbstractCloudMachineNamer.java @@ -0,0 +1,150 @@ +/* + * 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.cloud.names; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.objs.HasShortName; +import org.apache.brooklyn.core.location.cloud.CloudLocationConfig; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.text.Identifiers; +import org.apache.brooklyn.util.text.Strings; + +import com.google.common.annotations.Beta; +import com.google.common.base.CharMatcher; + +/** + * Implements <b>most</b> of {@link CloudMachineNamer}, + * leaving just one method -- {@link #generateNewIdOfLength(int)} -- + * for subclasses to provide. + * <p> + * {@link CloudLocationConfig#VM_NAME_MAX_LENGTH} is used to find the VM length, + * unless {@link #getCustomMaxNameLength(ConfigBag)} is overridden or + * {@link #setDefaultMachineNameMaxLength(int)} invoked on the instance supplied. + */ +public abstract class AbstractCloudMachineNamer implements CloudMachineNamer { + + int defaultMachineNameMaxLength = CloudLocationConfig.VM_NAME_MAX_LENGTH.getDefaultValue(); + int defaultMachineNameSaltLength = CloudLocationConfig.VM_NAME_SALT_LENGTH.getDefaultValue(); + protected String separator = "-"; + + public String generateNewMachineUniqueName(ConfigBag setup) { + return generateNewIdReservingLength(setup, 0); + } + + public String generateNewMachineUniqueNameFromGroupId(ConfigBag setup, String groupId) { + int availSaltLength = getMaxNameLength(setup) - (groupId.length() + separator.length()); + int requestedSaltLength = getLengthForMachineUniqueNameSalt(setup, false); + if (availSaltLength <= 0 || requestedSaltLength <= 0) { + return groupId; + } + + return sanitize(groupId + separator + Identifiers.makeRandomId(Math.min(requestedSaltLength, availSaltLength))).toLowerCase(); + } + + public String generateNewGroupId(ConfigBag setup) { + return sanitize(generateNewIdReservingLength(setup, getLengthForMachineUniqueNameSalt(setup, true))).toLowerCase(); + } + + protected String generateNewIdReservingLength(ConfigBag setup, int lengthToReserve) { + int len = getMaxNameLength(setup); + // decrement by e.g. 9 chars because jclouds adds that (dash plus 8 for hex id) + len -= lengthToReserve; + if (len<=0) return ""; + return Strings.maxlen(generateNewIdOfLength(setup, len), len); + } + + /** Method for subclasses to provide to construct the context-specific part of an identifier, + * for use in {@link #generateNewGroupId()} and {@link #generateNewMachineUniqueName()}. + * + * @param maxLengthHint an indication of the maximum length permitted for the ID generated, + * supplied for implementations which wish to use this information to decide what to truncate. + * (This class will truncate any return values longer than this.) + */ + protected abstract String generateNewIdOfLength(ConfigBag setup, int maxLengthHint); + + /** Returns the max length of a VM name for the cloud specified in setup; + * this value is typically decremented by 9 to make room for jclouds labels; + * delegates to {@link #getCustomMaxNameLength()} when + * {@link CloudLocationConfig#VM_NAME_MAX_LENGTH} is not set */ + public int getMaxNameLength(ConfigBag setup) { + if (setup.containsKey(CloudLocationConfig.VM_NAME_MAX_LENGTH)) { + // if a length is set explicitly, use that (but intercept default behaviour) + return setup.get(CloudLocationConfig.VM_NAME_MAX_LENGTH); + } + + Integer custom = getCustomMaxNameLength(setup); + if (custom!=null) return custom; + + // return the default + return defaultMachineNameMaxLength; + } + + // sometimes we create salt string, sometimes jclouds does + public int getLengthForMachineUniqueNameSalt(ConfigBag setup, boolean includeSeparator) { + int saltLen; + if (setup.containsKey(CloudLocationConfig.VM_NAME_SALT_LENGTH)) { + saltLen = setup.get(CloudLocationConfig.VM_NAME_SALT_LENGTH); + } else { + // default value comes from key, but custom default can be set + saltLen = defaultMachineNameSaltLength; + } + + if (saltLen>0 && includeSeparator) + saltLen += separator.length(); + + return saltLen; + } + + public AbstractCloudMachineNamer setDefaultMachineNameMaxLength(int defaultMaxLength) { + this.defaultMachineNameMaxLength = defaultMaxLength; + return this; + } + + /** Number of chars to use or reserve for the machine identifier when constructing a group identifier; + * jclouds for instance uses "-" plus 8 */ + public AbstractCloudMachineNamer setDefaultMachineNameSeparatorAndSaltLength(String separator, int defaultMachineUniqueNameSaltLength) { + this.separator = separator; + this.defaultMachineNameSaltLength = defaultMachineUniqueNameSaltLength; + return this; + } + + /** Method for overriding to provide custom logic when an explicit config key is not set for the machine length. */ + public Integer getCustomMaxNameLength(ConfigBag setup) { + return null; + } + + protected static String shortName(Object x) { + if (x instanceof HasShortName) { + return ((HasShortName)x).getShortName(); + } + if (x instanceof Entity) { + return ((Entity)x).getDisplayName(); + } + return x.toString(); + } + + @Beta //probably won't live here long-term + public static String sanitize(String s) { + return CharMatcher.inRange('A', 'Z') + .or(CharMatcher.inRange('a', 'z')) + .or(CharMatcher.inRange('0', '9')) + .negate() + .trimAndCollapseFrom(s, '-'); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/538324e1/core/src/main/java/org/apache/brooklyn/core/location/cloud/names/BasicCloudMachineNamer.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/location/cloud/names/BasicCloudMachineNamer.java b/core/src/main/java/org/apache/brooklyn/core/location/cloud/names/BasicCloudMachineNamer.java new file mode 100644 index 0000000..4777ad4 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/location/cloud/names/BasicCloudMachineNamer.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.core.location.cloud.names; + +import org.apache.brooklyn.api.entity.Application; +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.core.location.cloud.CloudLocationConfig; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.text.Identifiers; +import org.apache.brooklyn.util.text.StringShortener; +import org.apache.brooklyn.util.text.Strings; + +/** + * Standard implementation of {@link CloudMachineNamer}, + * which looks at several of the properties of the context (entity) + * and is clever about abbreviating them. */ +public class BasicCloudMachineNamer extends AbstractCloudMachineNamer { + + @Override + protected String generateNewIdOfLength(ConfigBag setup, int len) { + Object context = setup.peek(CloudLocationConfig.CALLER_CONTEXT); + Entity entity = null; + if (context instanceof Entity) entity = (Entity) context; + + StringShortener shortener = Strings.shortener().separator("-"); + shortener.append("system", "brooklyn"); + + // randId often not necessary, as an 8-char hex identifier is added later (in jclouds? can we override?) + // however it can be useful to have this early in the string, to prevent collisions in places where it is abbreviated + shortener.append("randId", Identifiers.makeRandomId(4)); + + String user = System.getProperty("user.name"); + if (!"brooklyn".equals(user)) + // include user; unless the user is 'brooklyn', as 'brooklyn-brooklyn-' is just silly! + shortener.append("user", user); + + if (entity!=null) { + Application app = entity.getApplication(); + if (app!=null) { + shortener.append("app", shortName(app)) + .append("appId", app.getId()); + } + shortener.append("entity", shortName(entity)) + .append("entityId", entity.getId()); + } else if (context!=null) { + shortener.append("context", context.toString()); + } + + shortener.truncate("user", 12) + .truncate("app", 16) + .truncate("entity", 16) + .truncate("appId", 4) + .truncate("entityId", 4) + .truncate("context", 12); + + shortener.canTruncate("user", 8) + .canTruncate("app", 5) + .canTruncate("entity", 5) + .canTruncate("system", 2) + .canTruncate("app", 3) + .canTruncate("entity", 3) + .canRemove("app") + .canTruncate("user", 4) + .canRemove("entity") + .canTruncate("context", 4) + .canTruncate("randId", 2) + .canRemove("user") + .canTruncate("appId", 2) + .canRemove("appId"); + + String s = shortener.getStringOfMaxLength(len); + return sanitize(s).toLowerCase(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/538324e1/core/src/main/java/org/apache/brooklyn/core/location/cloud/names/CloudMachineNamer.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/location/cloud/names/CloudMachineNamer.java b/core/src/main/java/org/apache/brooklyn/core/location/cloud/names/CloudMachineNamer.java new file mode 100644 index 0000000..d963a4e --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/location/cloud/names/CloudMachineNamer.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.core.location.cloud.names; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.core.location.cloud.CloudLocationConfig; +import org.apache.brooklyn.util.core.config.ConfigBag; + +/** + * Interface used to construct names for individual cloud machines and for groups of machines. + * <p> + * Implementations <b>must</b> provide a constructor which takes a single argument, + * being the {@link ConfigBag} for the context where the machine is being created + * (usually a {@link Location}). + * <p> + * With that bag, the config key {@link CloudLocationConfig#CALLER_CONTEXT} + * typically contains the {@link Entity} for which the machine is being created. + */ +public interface CloudMachineNamer { + + /** + * Generate a name for a new machine, based on context. + * <p> + * The name should normally be unique, as a context might produce multiple machines, + * for example basing it partially on information from the context but also including some random salt. + */ + public String generateNewMachineUniqueName(ConfigBag setup); + /** + * Generate a name stem for a group of machines, based on context. + * <p> + * The name does not need to be unique, as uniqueness will be applied by {@link #generateNewMachineUniqueNameFromGroupId(String)}. + */ + public String generateNewGroupId(ConfigBag setup); + + /** + * Generate a unique name from the given name stem. + * <p> + * The name stem is normally based on context information so the usual + * function of this method is to apply a suffix which helps to uniquely distinguish between machines + * in cases where the same name stem ({@link #generateNewGroupId()}) is used for multiple machines. + */ + public String generateNewMachineUniqueNameFromGroupId(ConfigBag setup, String groupId); + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/538324e1/core/src/main/java/org/apache/brooklyn/core/location/cloud/names/CustomMachineNamer.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/location/cloud/names/CustomMachineNamer.java b/core/src/main/java/org/apache/brooklyn/core/location/cloud/names/CustomMachineNamer.java new file mode 100644 index 0000000..0111f99 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/location/cloud/names/CustomMachineNamer.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.core.location.cloud.names; + +import java.util.Map; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.entity.EntityInternal; +import org.apache.brooklyn.core.location.cloud.CloudLocationConfig; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.core.text.TemplateProcessor; +import org.apache.brooklyn.util.text.Strings; + +import com.google.common.collect.ImmutableMap; +import com.google.common.reflect.TypeToken; + +/** Provides a machine namer which looks at a location config key {@link #MACHINE_NAME_TEMPLATE} + * to construct the hostname. + * For instance, setting this to <code>${config.entity_hostname}</code> + * will take the hostname from an <code>entity_hostname</code> key passed as entity <code>brooklyn.config</code>. + * <p> + * Note that this is not jclouds aware, so jclouds-specific cloud max lengths are not observed with this class. + */ +public class CustomMachineNamer extends BasicCloudMachineNamer { + + public static final ConfigKey<String> MACHINE_NAME_TEMPLATE = ConfigKeys.newStringConfigKey("custom.machine.namer.machine", + "Freemarker template format for custom machine name", "${entity.displayName}"); + @SuppressWarnings("serial") + public static final ConfigKey<Map<String, ?>> EXTRA_SUBSTITUTIONS = ConfigKeys.newConfigKey(new TypeToken<Map<String, ?>>() {}, + "custom.machine.namer.substitutions", "Additional substitutions to be used in the template", ImmutableMap.<String, Object>of()); + + @Override + protected String generateNewIdOfLength(ConfigBag setup, int len) { + Object context = setup.peek(CloudLocationConfig.CALLER_CONTEXT); + Entity entity = null; + if (context instanceof Entity) { + entity = (Entity) context; + } + + String template = setup.get(MACHINE_NAME_TEMPLATE); + + String processed; + if (entity == null) { + processed = TemplateProcessor.processTemplateContents(template, setup.get(EXTRA_SUBSTITUTIONS)); + } else { + processed = TemplateProcessor.processTemplateContents(template, (EntityInternal)entity, setup.get(EXTRA_SUBSTITUTIONS)); + } + + processed = Strings.removeFromStart(processed, "#ftl\n"); + + return sanitize(processed); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/538324e1/core/src/main/java/org/apache/brooklyn/core/location/dynamic/DynamicLocation.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/location/dynamic/DynamicLocation.java b/core/src/main/java/org/apache/brooklyn/core/location/dynamic/DynamicLocation.java new file mode 100644 index 0000000..b04ebac --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/location/dynamic/DynamicLocation.java @@ -0,0 +1,50 @@ +/* + * 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.dynamic; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.util.core.flags.SetFromFlag; + +import com.google.common.annotations.Beta; + +/** + * A location that is created and owned by an entity at runtime. + * <p> + * The lifecycle of the location is managed by the owning entity. + * + * @param E the entity type + * @param L the location type + */ +@Beta +public interface DynamicLocation<E extends Entity & LocationOwner<L, E>, L extends Location & DynamicLocation<E, L>> { + + @SetFromFlag("owner") + ConfigKey<Entity> OWNER = + ConfigKeys.newConfigKey(Entity.class, "owner", "The entity owning this location"); + + @SetFromFlag("maxLocations") + ConfigKey<Integer> MAX_SUB_LOCATIONS = + ConfigKeys.newIntegerConfigKey("maxLocations", "The maximum number of sub-locations that can be created; 0 for unlimited", 0); + + E getOwner(); + +}
