Repository: brooklyn-server Updated Branches: refs/heads/master fdf29403a -> 115a3712d
BROOKLYN-259: Fix JcloudsByonLocationResolver - Previously, it created locations every time the resolver was called (even if that was just to query about what it would produce), which caused us to leak locations. - Now it defers creating the locations (though doing this with LocationSpec is not practical because of the relationship between the JcloudsLocation and the machines)! - Adds FixedListMachineProvisioningLocation.initialMachinesFactory, for deferring the machine-lookup until the byon location is really being created. Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/9da32f6b Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/9da32f6b Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/9da32f6b Branch: refs/heads/master Commit: 9da32f6b8614886c71b4da89d567ffcc0f116a2f Parents: dee1074 Author: Aled Sage <aled.s...@gmail.com> Authored: Tue May 3 16:58:32 2016 +0100 Committer: Aled Sage <aled.s...@gmail.com> Committed: Thu May 19 16:42:24 2016 +0100 ---------------------------------------------------------------------- .../core/location/AbstractLocationResolver.java | 4 +- .../location/byon/ByonLocationResolver.java | 4 +- .../FixedListMachineProvisioningLocation.java | 44 +++- .../jclouds/JcloudsByonLocationResolver.java | 204 ++++++++-------- .../location/jclouds/JcloudsLocation.java | 4 +- .../jclouds/AbstractJcloudsStubbedLiveTest.java | 34 ++- ...dsByonLocationResolverStubbedRebindTest.java | 235 +++++++++++++++++++ .../JcloudsByonLocationResolverStubbedTest.java | 157 +++++++++++++ 8 files changed, 568 insertions(+), 118 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9da32f6b/core/src/main/java/org/apache/brooklyn/core/location/AbstractLocationResolver.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/location/AbstractLocationResolver.java b/core/src/main/java/org/apache/brooklyn/core/location/AbstractLocationResolver.java index 809e228..0a836e6 100644 --- a/core/src/main/java/org/apache/brooklyn/core/location/AbstractLocationResolver.java +++ b/core/src/main/java/org/apache/brooklyn/core/location/AbstractLocationResolver.java @@ -92,8 +92,8 @@ public abstract class AbstractLocationResolver implements LocationResolver { } return LocationSpec.create(getLocationType()) - .configure(config.getAllConfig()) - .configure(LocationConfigUtils.finalAndOriginalSpecs(spec, locationFlags, globalProperties, namedLocation)); + .configure(config.getAllConfig()) + .configure(LocationConfigUtils.finalAndOriginalSpecs(spec, locationFlags, globalProperties, namedLocation)); } protected ConfigBag extractConfig(Map<?,?> locationFlags, String spec, LocationRegistry registry) { http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9da32f6b/core/src/main/java/org/apache/brooklyn/location/byon/ByonLocationResolver.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/byon/ByonLocationResolver.java b/core/src/main/java/org/apache/brooklyn/location/byon/ByonLocationResolver.java index 90c9a53..58b4989 100644 --- a/core/src/main/java/org/apache/brooklyn/location/byon/ByonLocationResolver.java +++ b/core/src/main/java/org/apache/brooklyn/location/byon/ByonLocationResolver.java @@ -108,7 +108,7 @@ public class ByonLocationResolver extends AbstractLocationResolver { defaultProps.addIfNotNull("user", user); defaultProps.addIfNotNull("port", port); - List<String> hostAddresses; + List<?> hostAddresses; if (hosts instanceof String) { if (((String) hosts).isEmpty()) { @@ -118,7 +118,7 @@ public class ByonLocationResolver extends AbstractLocationResolver { true /* numeric */, /* no quote support though */ PhraseTreatment.NOT_A_SPECIAL_CHAR, PhraseTreatment.NOT_A_SPECIAL_CHAR); } } else if (hosts instanceof Iterable) { - hostAddresses = ImmutableList.copyOf((Iterable<String>)hosts); + hostAddresses = ImmutableList.copyOf((Iterable<?>)hosts); } else { throw new IllegalArgumentException("Invalid location '"+spec+"'; at least one host must be defined"); } http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9da32f6b/core/src/main/java/org/apache/brooklyn/location/byon/FixedListMachineProvisioningLocation.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/byon/FixedListMachineProvisioningLocation.java b/core/src/main/java/org/apache/brooklyn/location/byon/FixedListMachineProvisioningLocation.java index 6e88f15..15db018 100644 --- a/core/src/main/java/org/apache/brooklyn/location/byon/FixedListMachineProvisioningLocation.java +++ b/core/src/main/java/org/apache/brooklyn/location/byon/FixedListMachineProvisioningLocation.java @@ -54,6 +54,7 @@ import org.slf4j.LoggerFactory; import com.google.common.annotations.Beta; import com.google.common.base.Function; import com.google.common.base.Objects; +import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -80,6 +81,7 @@ implements MachineProvisioningLocation<T>, Closeable { private static final Logger log = LoggerFactory.getLogger(FixedListMachineProvisioningLocation.class); + @SuppressWarnings("serial") public static final ConfigKey<Function<Iterable<? extends MachineLocation>, MachineLocation>> MACHINE_CHOOSER = ConfigKeys.newConfigKey( new TypeToken<Function<Iterable<? extends MachineLocation>, MachineLocation>>() {}, @@ -103,10 +105,29 @@ implements MachineProvisioningLocation<T>, Closeable { @Beta @SuppressWarnings("serial") public static final ConfigKey<List<LocationSpec<? extends MachineLocation>>> MACHINE_SPECS = ConfigKeys.newConfigKey( - new TypeToken<List<LocationSpec<? extends MachineLocation>>>() {}, - "byon.machineSpecs", - "Specs of machines that should be immediatly instantiated on init", - ImmutableList.<LocationSpec<? extends MachineLocation>>of()); + new TypeToken<List<LocationSpec<? extends MachineLocation>>>() {}, + "byon.machineSpecs", + "Specs of machines that should be immediatly instantiated on init", + ImmutableList.<LocationSpec<? extends MachineLocation>>of()); + + /** + * The initialMachinesFactory allows {@code JcloudsByonLocationResolver} to work, to defer + * instantiating the {@code JcloudsLocation} and the {@code JcloudsMachineLocation} instances. + * (Important because the caller might not use the spec and thus might not unmanage the machine + * instances). + * + * We clear the initialMachinesFactory in init, so they will never be persisted. This will help + * with backwards compatibility if we change how this is done. + * + * By the end of init(), the {@link #machines} will contain the full list of locations. + */ + @Beta + @SuppressWarnings("serial") + public static final ConfigKey<Supplier<? extends List<? extends MachineLocation>>> INITIAL_MACHINES_FACTORY = ConfigKeys.newConfigKey( + new TypeToken<Supplier<? extends List<? extends MachineLocation>>>() {}, + "byon.initialMachinesFactory", + "Factory for creating the machines that should be immediatly instantiated on init", + null); private final Object lock = new Object(); @@ -147,8 +168,21 @@ implements MachineProvisioningLocation<T>, Closeable { } config().set(MACHINE_SPECS, (List<LocationSpec<? extends MachineLocation>>) null); + Supplier<? extends List<? extends MachineLocation>> initialMachinesFactory = getConfig(INITIAL_MACHINES_FACTORY); + if (initialMachinesFactory != null) { + List<? extends MachineLocation> initialMachines = initialMachinesFactory.get(); + if (initialMachines != null) { + for (MachineLocation machine : initialMachines) { + @SuppressWarnings("unchecked") + T castMachine = (T) machine; + machines.add(castMachine); + } + } + } + config().set(INITIAL_MACHINES_FACTORY, (Supplier<List<? extends MachineLocation>>) null); + Set<T> machinesCopy = MutableSet.of(); - for (T location: machines) { + for (T location : machines) { if (location==null) { log.warn(""+this+" initialized with null location, removing (may be due to rebind with reference to an unmanaged location)"); } else { http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9da32f6b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsByonLocationResolver.java ---------------------------------------------------------------------- diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsByonLocationResolver.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsByonLocationResolver.java index c4e09fb..d832a04 100644 --- a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsByonLocationResolver.java +++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsByonLocationResolver.java @@ -18,37 +18,32 @@ */ package org.apache.brooklyn.location.jclouds; -import static com.google.common.base.Preconditions.checkNotNull; - import java.util.List; import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import org.apache.brooklyn.api.location.Location; import org.apache.brooklyn.api.location.LocationRegistry; import org.apache.brooklyn.api.location.LocationResolver; import org.apache.brooklyn.api.location.LocationSpec; import org.apache.brooklyn.api.location.NoMachinesAvailableException; -import org.apache.brooklyn.api.mgmt.ManagementContext; -import org.apache.brooklyn.core.location.BasicLocationRegistry; -import org.apache.brooklyn.core.location.LocationConfigKeys; +import org.apache.brooklyn.core.config.Sanitizer; +import org.apache.brooklyn.core.location.AbstractLocationResolver; import org.apache.brooklyn.core.location.LocationConfigUtils; -import org.apache.brooklyn.core.location.LocationPropertiesFromBrooklynProperties; import org.apache.brooklyn.core.location.internal.LocationInternal; import org.apache.brooklyn.location.byon.FixedListMachineProvisioningLocation; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.core.config.ConfigBag; import org.apache.brooklyn.util.exceptions.Exceptions; -import org.apache.brooklyn.util.text.KeyValueParser; +import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.text.WildcardGlobs; import org.apache.brooklyn.util.text.WildcardGlobs.PhraseTreatment; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; -import com.google.common.collect.Maps; /** * Examples of valid specs: @@ -61,129 +56,140 @@ import com.google.common.collect.Maps; * @author aled */ @SuppressWarnings({"unchecked","rawtypes"}) -public class JcloudsByonLocationResolver implements LocationResolver { +public class JcloudsByonLocationResolver extends AbstractLocationResolver implements LocationResolver { public static final Logger log = LoggerFactory.getLogger(JcloudsByonLocationResolver.class); public static final String BYON = "jcloudsByon"; - private static final Pattern PATTERN = Pattern.compile("("+BYON+"|"+BYON.toUpperCase()+")" + ":" + "\\((.*)\\)$"); - - private ManagementContext managementContext; - @Override - public void init(ManagementContext managementContext) { - this.managementContext = checkNotNull(managementContext, "managementContext"); + public boolean isEnabled() { + return LocationConfigUtils.isResolverPrefixEnabled(managementContext, getPrefix()); } @Override - public boolean isEnabled() { - return LocationConfigUtils.isResolverPrefixEnabled(managementContext, getPrefix()); + public String getPrefix() { + return BYON; } - // TODO Remove some duplication from JcloudsResolver; needs more careful review @Override - public LocationSpec<? extends Location> newLocationSpecFromString(String spec, Map<?, ?> locationFlags, LocationRegistry registry) { - Map globalProperties = registry.getProperties(); + protected Class<? extends Location> getLocationType() { + return FixedListMachineProvisioningLocation.class; + } - Matcher matcher = PATTERN.matcher(spec); - if (!matcher.matches()) { - throw new IllegalArgumentException("Invalid location '"+spec+"'; must specify something like jcloudsByon(provider=\"aws-ec2\",region=\"us-east-1\",hosts=\"i-f2014593,i-d1234567\")"); - } - - String argsPart = matcher.group(2); - Map<String, String> argsMap = KeyValueParser.parseMap(argsPart); - - // prefer args map over location flags - - String namedLocation = (String) locationFlags.get(LocationInternal.NAMED_SPEC_NAME.getName()); + @Override + protected SpecParser getSpecParser() { + return new AbstractLocationResolver.SpecParser(getPrefix()).setExampleUsage("\"jcloudsByon(provider='aws-ec2',region='us-east-1',hosts='i-12345678,i-90123456')\""); + } + + @Override + protected ConfigBag extractConfig(Map<?,?> locationFlags, String spec, final LocationRegistry registry) { + ConfigBag config = super.extractConfig(locationFlags, spec, registry); - String providerOrApi = argsMap.containsKey("provider") ? argsMap.get("provider") : (String)locationFlags.get("provider"); + String providerOrApi = (String) config.getStringKey("provider"); + String regionName = (String) config.getStringKey("region"); + String endpoint = (String) config.getStringKey("endpoint"); + String namedLocation = (String) config.get(LocationInternal.NAMED_SPEC_NAME); + config.remove(LocationInternal.NAMED_SPEC_NAME.getName()); - String regionName = argsMap.containsKey("region") ? argsMap.get("region") : (String)locationFlags.get("region"); - - String endpoint = argsMap.containsKey("endpoint") ? argsMap.get("endpoint") : (String)locationFlags.get("endpoint"); - - String name = argsMap.containsKey("name") ? argsMap.get("name") : (String)locationFlags.get("name"); + Object hosts = config.getStringKey("hosts"); + config.remove("hosts"); - String user = argsMap.containsKey("user") ? argsMap.get("user") : (String)locationFlags.get("user"); + Map jcloudsProperties = new JcloudsPropertiesFromBrooklynProperties().getJcloudsProperties(providerOrApi, regionName, namedLocation, config.getAllConfig()); + config.putIfAbsent(jcloudsProperties); - String privateKeyFile = argsMap.containsKey("privateKeyFile") ? argsMap.get("privateKeyFile") : (String)locationFlags.get("privateKeyFile"); - - String hosts = argsMap.get("hosts"); - if (Strings.isEmpty(providerOrApi)) { throw new IllegalArgumentException("Invalid location '"+spec+"'; provider must be defined"); } - if (hosts == null || hosts.isEmpty()) { + if (hosts == null || (hosts instanceof String && Strings.isBlank((String)hosts))) { throw new IllegalArgumentException("Invalid location '"+spec+"'; at least one host must be defined"); } - if (argsMap.containsKey("name") && (Strings.isEmpty(name))) { - throw new IllegalArgumentException("Invalid location '"+spec+"'; if name supplied then value must be non-empty"); + + final String jcloudsSpec = "jclouds:"+providerOrApi + (regionName != null ? ":"+regionName : "") + (endpoint != null ? ":"+endpoint : ""); + final Maybe<LocationSpec<? extends Location>> jcloudsLocationSpec = registry.getLocationSpec(jcloudsSpec, config.getAllConfig()); + if (jcloudsLocationSpec.isAbsent()) { + throw new IllegalArgumentException("Invalid location '"+spec+"'; referrenced jclouds spec '"+jcloudsSpec+"' cannot be resolved"); + } else if (!JcloudsLocation.class.isAssignableFrom(jcloudsLocationSpec.get().getType())) { + throw new IllegalArgumentException("Invalid location '"+spec+"'; referrenced spec '"+jcloudsSpec+"' of type "+jcloudsLocationSpec.get().getType()+" rather than JcloudsLocation"); } - // For everything in brooklyn.properties, only use things with correct prefix (and remove that prefix). - // But for everything passed in via locationFlags, pass those as-is. - // TODO Should revisit the locationFlags: where are these actually used? Reason accepting properties without - // full prefix is that the map's context is explicitly this location, rather than being generic properties. - Map allProperties = getAllProperties(registry, globalProperties); - Map jcloudsProperties = new JcloudsPropertiesFromBrooklynProperties().getJcloudsProperties(providerOrApi, regionName, namedLocation, allProperties); - jcloudsProperties.putAll(locationFlags); - jcloudsProperties.putAll(argsMap); + List<?> hostIds; - String jcloudsSpec = "jclouds:"+providerOrApi + (regionName != null ? ":"+regionName : "") + (endpoint != null ? ":"+endpoint : ""); - JcloudsLocation jcloudsLocation = (JcloudsLocation) registry.resolve(jcloudsSpec, jcloudsProperties); + if (hosts instanceof String) { + if (((String) hosts).isEmpty()) { + hostIds = ImmutableList.of(); + } else { + hostIds = WildcardGlobs.getGlobsAfterBraceExpansion("{"+hosts+"}", + true /* numeric */, /* no quote support though */ PhraseTreatment.NOT_A_SPECIAL_CHAR, PhraseTreatment.NOT_A_SPECIAL_CHAR); + } + } else if (hosts instanceof Iterable) { + hostIds = ImmutableList.copyOf((Iterable<?>)hosts); + } else { + throw new IllegalArgumentException("Invalid location '"+spec+"'; at least one host must be defined"); + } + if (hostIds.isEmpty()) { + throw new IllegalArgumentException("Invalid location '"+spec+"'; at least one host must be defined"); + } - List<String> hostIdentifiers = WildcardGlobs.getGlobsAfterBraceExpansion("{"+hosts+"}", - true /* numeric */, /* no quote support though */ PhraseTreatment.NOT_A_SPECIAL_CHAR, PhraseTreatment.NOT_A_SPECIAL_CHAR); - List<JcloudsSshMachineLocation> machines = Lists.newArrayList(); - - for (String hostIdentifier : hostIdentifiers) { - Map<?, ?> machineFlags = MutableMap.builder() - .put("id", hostIdentifier) - .putIfNotNull("user", user) - .putIfNotNull("privateKeyFile", privateKeyFile) - .build(); - try { - // TODO management of these machines may be odd, as it is passed in as a key as config to a spec - JcloudsSshMachineLocation machine = jcloudsLocation.rebindMachine(jcloudsLocation.config().getBag().putAll(machineFlags)); - machines.add(machine); - } catch (NoMachinesAvailableException e) { - log.warn("Error rebinding to jclouds machine "+hostIdentifier+" in "+jcloudsLocation, e); - Exceptions.propagate(e); + final List<Map<?,?>> machinesFlags = Lists.newArrayList(); + for (Object hostId : hostIds) { + Map<?, ?> machineFlags; + if (hostId instanceof String) { + machineFlags = parseMachineFlags((String)hostId, config); + } else if (hostId instanceof Map) { + machineFlags = parseMachineFlags((Map<String,?>)hostId, config); + } else { + throw new IllegalArgumentException("Invalid host type '"+(hostId == null ? null : hostId.getClass().getName())+", referrenced in spec '"+spec); } + machinesFlags.add(machineFlags); } + Supplier<List<JcloudsMachineLocation>> machinesFactory = new Supplier<List<JcloudsMachineLocation>>() { + @Override + public List<JcloudsMachineLocation> get() { + // FIXME Can't change the parent of the JcloudsMachineLocation; but the FixedListMachineLocation will expect it to be the parent? + // We'll get errors on rebind. Need to reproduce/investigate. + List<JcloudsMachineLocation> result = Lists.newArrayList(); + JcloudsLocation jcloudsLocation = (JcloudsLocation) managementContext.getLocationManager().createLocation(jcloudsLocationSpec.get()); + for (Map<?,?> machineFlags : machinesFlags) { + try { + JcloudsMachineLocation machine = jcloudsLocation.registerMachine(jcloudsLocation.config().getBag().putAll(machineFlags)); + result.add(machine); + } catch (NoMachinesAvailableException e) { + Map<?,?> sanitizedMachineFlags = Sanitizer.sanitize(machineFlags); + log.warn("Error rebinding to jclouds machine "+sanitizedMachineFlags+" in "+jcloudsLocation, e); + Exceptions.propagate(e); + } + } + + log.debug("Created machines for jclouds BYON location: "+result); + return result; + } + }; + ConfigBag flags = ConfigBag.newInstance(jcloudsProperties); - flags.putStringKey("machines", machines); - flags.putIfNotNull(LocationConfigKeys.USER, user); - flags.putStringKeyIfNotNull("name", name); - - if (registry != null) - LocationPropertiesFromBrooklynProperties.setLocalTempDir(registry.getProperties(), flags); + config.put(FixedListMachineProvisioningLocation.INITIAL_MACHINES_FACTORY, machinesFactory); - log.debug("Created Jclouds BYON location "+name+": "+machines); - - return LocationSpec.create(FixedListMachineProvisioningLocation.class) - .configure(flags.getAllConfig()) - .configure(LocationConfigUtils.finalAndOriginalSpecs(spec, locationFlags, globalProperties, namedLocation)); + return config; } - private Map getAllProperties(LocationRegistry registry, Map<?,?> properties) { - Map<Object,Object> allProperties = Maps.newHashMap(); - if (registry!=null) allProperties.putAll(registry.getProperties()); - allProperties.putAll(properties); - return allProperties; - } - @Override - public String getPrefix() { - return BYON; + protected Map<?, ?> parseMachineFlags(Map<String, ?> vals, ConfigBag config) { + if (!(vals.get("id") instanceof String) || Strings.isBlank((String)vals.get("id"))) { + Map<String, Object> valSanitized = Sanitizer.sanitize(vals); + throw new IllegalArgumentException("In jcloudsByon, machine "+valSanitized+" is missing String 'id'"); + } + return MutableMap.builder() + .putAll(config.getAllConfig()) + .putAll(vals) + .build(); } - - @Override - public boolean accepts(String spec, LocationRegistry registry) { - return BasicLocationRegistry.isResolverPrefixForSpec(this, spec, true); + + protected Map<?, ?> parseMachineFlags(String hostIdentifier, ConfigBag config) { + return MutableMap.builder() + .putAll(config.getAllConfig()) + .put("id", hostIdentifier) + .build(); } -} \ No newline at end of file +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9da32f6b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java ---------------------------------------------------------------------- diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java index ebce579..d55589a 100644 --- a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java +++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java @@ -2116,12 +2116,12 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im * <li>userName: the username for sshing into the machine (for use if it is not a Windows system) * <ul> */ - public MachineLocation registerMachine(ConfigBag setup) throws NoMachinesAvailableException { + public JcloudsMachineLocation registerMachine(ConfigBag setup) throws NoMachinesAvailableException { NodeMetadata node = findNodeOrThrow(setup); return registerMachineLocation(setup, node); } - protected MachineLocation registerMachineLocation(ConfigBag setup, NodeMetadata node) { + protected JcloudsMachineLocation registerMachineLocation(ConfigBag setup, NodeMetadata node) { ComputeService computeService = getComputeService(setup); if (isWindows(node, setup)) { return registerWinRmMachineLocation(computeService, node, null, Optional.<HostAndPort>absent(), setup); http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9da32f6b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/AbstractJcloudsStubbedLiveTest.java ---------------------------------------------------------------------- diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/AbstractJcloudsStubbedLiveTest.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/AbstractJcloudsStubbedLiveTest.java index c885197..6aa62fe 100644 --- a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/AbstractJcloudsStubbedLiveTest.java +++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/AbstractJcloudsStubbedLiveTest.java @@ -25,6 +25,7 @@ import java.util.Set; import org.apache.brooklyn.util.core.config.ConfigBag; import org.jclouds.compute.ComputeService; import org.jclouds.compute.RunNodesException; +import org.jclouds.compute.domain.ComputeMetadata; import org.jclouds.compute.domain.NodeMetadata; import org.jclouds.compute.domain.Template; import org.jclouds.compute.options.TemplateOptions; @@ -34,6 +35,7 @@ import org.testng.annotations.BeforeMethod; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Sets; @@ -65,6 +67,9 @@ public abstract class AbstractJcloudsStubbedLiveTest extends AbstractJcloudsLive } return result; } + public Set<? extends NodeMetadata> listNodesDetailsMatching(Predicate<ComputeMetadata> filter) { + return ImmutableSet.of(); + } public void destroyNode(String id) { destroyed.add(id); } @@ -83,6 +88,10 @@ public abstract class AbstractJcloudsStubbedLiveTest extends AbstractJcloudsLive return nodeCreator.createNodesInGroup(group, count, template); } @Override + public Set<? extends NodeMetadata> listNodesDetailsMatching(Predicate<ComputeMetadata> filter) { + return nodeCreator.listNodesDetailsMatching(filter); + } + @Override public void destroyNode(String id) { nodeCreator.destroyNode(id); } @@ -99,21 +108,30 @@ public abstract class AbstractJcloudsStubbedLiveTest extends AbstractJcloudsLive throw new UnsupportedOperationException(); } } - + + public static class StubbedComputeServiceRegistry implements ComputeServiceRegistry { + private final NodeCreator nodeCreator; + + public StubbedComputeServiceRegistry(NodeCreator nodeCreator) { + this.nodeCreator = nodeCreator; + } + @Override + public ComputeService findComputeService(ConfigBag conf, boolean allowReuse) { + ComputeService delegate = ComputeServiceRegistryImpl.INSTANCE.findComputeService(conf, allowReuse); + return new StubbedComputeService(delegate, nodeCreator); + } + } + protected NodeCreator nodeCreator; + protected ComputeServiceRegistry computeServiceRegistry; @BeforeMethod(alwaysRun=true) @Override public void setUp() throws Exception { super.setUp(); nodeCreator = newNodeCreator(); - ComputeServiceRegistry computeServiceRegistry = new ComputeServiceRegistry() { - @Override - public ComputeService findComputeService(ConfigBag conf, boolean allowReuse) { - ComputeService delegate = ComputeServiceRegistryImpl.INSTANCE.findComputeService(conf, allowReuse); - return new StubbedComputeService(delegate, nodeCreator); - } - }; + computeServiceRegistry = new StubbedComputeServiceRegistry(nodeCreator); + jcloudsLocation = (JcloudsLocation) managementContext.getLocationRegistry().getLocationManaged( getLocationSpec(), jcloudsLocationConfig(ImmutableMap.<Object, Object>of( http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9da32f6b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsByonLocationResolverStubbedRebindTest.java ---------------------------------------------------------------------- diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsByonLocationResolverStubbedRebindTest.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsByonLocationResolverStubbedRebindTest.java new file mode 100644 index 0000000..0e5c4af --- /dev/null +++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsByonLocationResolverStubbedRebindTest.java @@ -0,0 +1,235 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.location.jclouds; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + +import java.io.File; +import java.util.Map; +import java.util.Set; + +import org.apache.brooklyn.api.entity.Application; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.location.MachineLocation; +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.core.entity.Entities; +import org.apache.brooklyn.core.internal.BrooklynProperties; +import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext; +import org.apache.brooklyn.core.mgmt.persist.FileBasedObjectStore; +import org.apache.brooklyn.core.mgmt.rebind.RebindOptions; +import org.apache.brooklyn.core.mgmt.rebind.RebindTestUtils; +import org.apache.brooklyn.entity.stock.BasicApplication; +import org.apache.brooklyn.location.byon.FixedListMachineProvisioningLocation; +import org.apache.brooklyn.util.os.Os; +import org.apache.brooklyn.util.text.Identifiers; +import org.apache.brooklyn.util.time.Duration; +import org.jclouds.compute.domain.ComputeMetadata; +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.domain.NodeMetadata.Status; +import org.jclouds.compute.domain.NodeMetadataBuilder; +import org.jclouds.compute.domain.Template; +import org.jclouds.domain.LoginCredentials; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; + +public class JcloudsByonLocationResolverStubbedRebindTest extends AbstractJcloudsStubbedLiveTest { + + private static final Logger LOG = LoggerFactory.getLogger(JcloudsByonLocationResolverStubbedRebindTest.class); + + // Using static so that the static NodeCreator class can access it. + // We are using the static NodeCreator to ensure it is serializable, without it trying to serialize JcloudsByonLocationResolverStubbedRebindLiveTest. + private static final String nodeId = "mynodeid"; + private static final String nodePublicAddress = "173.194.32.123"; + private static final String nodePrivateAddress = "172.168.10.11"; + + protected static final Duration TIMEOUT_MS = Duration.TEN_SECONDS; + + protected ClassLoader classLoader = getClass().getClassLoader(); + protected LocalManagementContext origManagementContext; + protected File mementoDir; + protected File mementoDirBackup; + + protected Application origApp; + protected Application newApp; + protected ManagementContext newManagementContext; + + @BeforeMethod(alwaysRun=true) + public void setUp() throws Exception { + mementoDir = Os.newTempDir(getClass()); + File mementoDirParent = mementoDir.getParentFile(); + mementoDirBackup = new File(mementoDirParent, mementoDir.getName()+"."+Identifiers.makeRandomId(4)+".bak"); + + origManagementContext = createOrigManagementContext(); + origApp = origManagementContext.getEntityManager().createEntity(EntitySpec.create(BasicApplication.class)); + LOG.info("Test "+getClass()+" persisting to "+mementoDir); + + super.setUp(); + } + + @AfterMethod(alwaysRun=true) + public void tearDown() throws Exception { + super.tearDown(); + if (origApp != null) Entities.destroyAll(origApp.getManagementContext()); + if (newApp != null) Entities.destroyAll(newApp.getManagementContext()); + if (newManagementContext != null) Entities.destroyAll(newManagementContext); + origApp = null; + newApp = null; + newManagementContext = null; + + if (origManagementContext != null) Entities.destroyAll(origManagementContext); + if (mementoDir != null) FileBasedObjectStore.deleteCompletely(mementoDir); + if (mementoDirBackup != null) FileBasedObjectStore.deleteCompletely(mementoDir); + origManagementContext = null; + } + + @Override + protected NodeCreator newNodeCreator() { + return new NodeCreatorForRebinding(); + } + public static class NodeCreatorForRebinding extends NodeCreator { + @Override + public Set<? extends NodeMetadata> listNodesDetailsMatching(Predicate<ComputeMetadata> filter) { + NodeMetadata result = new NodeMetadataBuilder() + .id(nodeId) + .credentials(LoginCredentials.builder().identity("dummy").credential("dummy").build()) + .loginPort(22) + .status(Status.RUNNING) + .publicAddresses(ImmutableList.of(nodePublicAddress)) + .privateAddresses(ImmutableList.of(nodePrivateAddress)) + .build(); + return ImmutableSet.copyOf(Iterables.filter(ImmutableList.of(result), filter)); + } + @Override + protected NodeMetadata newNode(String group, Template template) { + throw new UnsupportedOperationException(); + } + }; + + @Test + public void testRebind() throws Exception { + String spec = "jcloudsByon:(provider=\""+SOFTLAYER_PROVIDER+"\",region=\""+SOFTLAYER_AMS01_REGION_NAME+"\",user=\"myuser\",password=\"mypassword\",hosts=\""+nodeId+"\")"; + Map<?,?> specFlags = ImmutableMap.of(JcloudsLocationConfig.COMPUTE_SERVICE_REGISTRY, computeServiceRegistry); + + FixedListMachineProvisioningLocation<MachineLocation> location = getLocationManaged(spec, specFlags); + JcloudsSshMachineLocation machine = (JcloudsSshMachineLocation) Iterables.getOnlyElement(location.getAllMachines()); + + rebind(); + + FixedListMachineProvisioningLocation<?> newLocation = (FixedListMachineProvisioningLocation<?>) newManagementContext.getLocationManager().getLocation(location.getId()); + JcloudsSshMachineLocation newMachine = (JcloudsSshMachineLocation) newManagementContext.getLocationManager().getLocation(machine.getId()); + assertNotNull(newLocation); + assertEquals(newMachine.getJcloudsId(), nodeId); + } + + @SuppressWarnings("unchecked") + private FixedListMachineProvisioningLocation<MachineLocation> getLocationManaged(String val, Map<?,?> specFlags) { + return (FixedListMachineProvisioningLocation<MachineLocation>) managementContext.getLocationRegistry().getLocationManaged(val, specFlags); + } + + + ///////////////////////////////////////////////////////////////////////////////////////////// + // Everything below this point is copied from RebindTestFixture (or is a tweak required to // + // make it work like that). We need this because we are extending // + // AbstractJcloudsStubbedLiveTest. In comparison, most rebind tests extend // + // RebindTestFixtureWithApp. // + ///////////////////////////////////////////////////////////////////////////////////////////// + + // called by super.setUp + @Override + protected LocalManagementContext newManagementContext() { + return origManagementContext; + } + + protected Application rebind() throws Exception { + return rebind(RebindOptions.create()); + } + + protected Application rebind(RebindOptions options) throws Exception { + if (newApp != null || newManagementContext != null) { + throw new IllegalStateException("already rebound - use switchOriginalToNewManagementContext() if you are trying to rebind multiple times"); + } + + options = RebindOptions.create(options); + if (options.classLoader == null) options.classLoader(classLoader); + if (options.mementoDir == null) options.mementoDir(mementoDir); + if (options.origManagementContext == null) options.origManagementContext(origManagementContext); + if (options.newManagementContext == null) options.newManagementContext(createNewManagementContext(options.mementoDir)); + + RebindTestUtils.waitForPersisted(origApp); + + newManagementContext = options.newManagementContext; + newApp = RebindTestUtils.rebind(options); + return newApp; + } + + /** @return A started management context */ + protected LocalManagementContext createOrigManagementContext() { + return RebindTestUtils.managementContextBuilder(mementoDir, classLoader) + .persistPeriodMillis(getPersistPeriodMillis()) + .forLive(useLiveManagementContext()) + .emptyCatalog(useEmptyCatalog()) + .properties(createBrooklynProperties()) + .buildStarted(); + } + + /** As {@link #createNewManagementContext(File)} using the default memento dir */ + protected LocalManagementContext createNewManagementContext() { + return createNewManagementContext(mementoDir); + } + + /** @return An unstarted management context using the specified mementoDir (or default if null) */ + protected LocalManagementContext createNewManagementContext(File mementoDir) { + if (mementoDir==null) mementoDir = this.mementoDir; + return RebindTestUtils.managementContextBuilder(mementoDir, classLoader) + .forLive(useLiveManagementContext()) + .emptyCatalog(useEmptyCatalog()) + .properties(createBrooklynProperties()) + .buildUnstarted(); + } + + private BrooklynProperties createBrooklynProperties() { + // This really is stubbed; no live access to the cloud + BrooklynProperties brooklynProperties = BrooklynProperties.Factory.newEmpty(); + brooklynProperties.put("brooklyn.location.jclouds."+SOFTLAYER_PROVIDER+".identity", "myidentity"); + brooklynProperties.put("brooklyn.location.jclouds."+SOFTLAYER_PROVIDER+".credential", "mycredential"); + return brooklynProperties; + } + + protected boolean useLiveManagementContext() { + return false; + } + + protected boolean useEmptyCatalog() { + return true; + } + + protected int getPersistPeriodMillis() { + return 1; + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9da32f6b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsByonLocationResolverStubbedTest.java ---------------------------------------------------------------------- diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsByonLocationResolverStubbedTest.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsByonLocationResolverStubbedTest.java new file mode 100644 index 0000000..86a66f7 --- /dev/null +++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsByonLocationResolverStubbedTest.java @@ -0,0 +1,157 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.location.jclouds; + +import static org.testng.Assert.assertEquals; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.location.LocationSpec; +import org.apache.brooklyn.api.location.MachineLocation; +import org.apache.brooklyn.core.internal.BrooklynProperties; +import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext; +import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; +import org.apache.brooklyn.location.byon.FixedListMachineProvisioningLocation; +import org.jclouds.compute.domain.ComputeMetadata; +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.domain.NodeMetadata.Status; +import org.jclouds.compute.domain.NodeMetadataBuilder; +import org.jclouds.compute.domain.Template; +import org.jclouds.domain.LoginCredentials; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.Test; + +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; + +public class JcloudsByonLocationResolverStubbedTest extends AbstractJcloudsStubbedLiveTest { + + @SuppressWarnings("unused") + private static final Logger log = LoggerFactory.getLogger(JcloudsByonLocationResolverStubbedTest.class); + + private final String nodeId = "mynodeid"; + private final String nodePublicAddress = "173.194.32.123"; + private final String nodePrivateAddress = "172.168.10.11"; + + protected LocalManagementContext newManagementContext() { + // This really is stubbed; no live access to the cloud + LocalManagementContext result = LocalManagementContextForTests.builder(true).build(); + BrooklynProperties brooklynProperties = result.getBrooklynProperties(); + brooklynProperties.put("brooklyn.location.jclouds."+SOFTLAYER_PROVIDER+".identity", "myidentity"); + brooklynProperties.put("brooklyn.location.jclouds."+SOFTLAYER_PROVIDER+".credential", "mycredential"); + return result; + + } + + @Override + protected NodeCreator newNodeCreator() { + return new NodeCreator() { + @Override + public Set<? extends NodeMetadata> listNodesDetailsMatching(Predicate<ComputeMetadata> filter) { + NodeMetadata result = new NodeMetadataBuilder() + .id(nodeId) + .credentials(LoginCredentials.builder().identity("dummy").credential("dummy").build()) + .loginPort(22) + .status(Status.RUNNING) + .publicAddresses(ImmutableList.of(nodePublicAddress)) + .privateAddresses(ImmutableList.of(nodePrivateAddress)) + .build(); + return ImmutableSet.copyOf(Iterables.filter(ImmutableList.of(result), filter)); + } + @Override + protected NodeMetadata newNode(String group, Template template) { + throw new UnsupportedOperationException(); + } + }; + } + + @Test + public void testResolvesHostInSpec() throws Exception { + String spec = "jcloudsByon:(provider=\""+SOFTLAYER_PROVIDER+"\",region=\""+SOFTLAYER_AMS01_REGION_NAME+"\",user=\"myuser\",password=\"mypassword\",hosts=\""+nodeId+"\")"; + Map<?,?> specFlags = ImmutableMap.of(JcloudsLocationConfig.COMPUTE_SERVICE_REGISTRY, computeServiceRegistry); + + FixedListMachineProvisioningLocation<MachineLocation> location = getLocationManaged(spec, specFlags); + + JcloudsSshMachineLocation machine = (JcloudsSshMachineLocation) Iterables.getOnlyElement(location.getAllMachines()); + assertEquals(machine.getJcloudsId(), nodeId); + assertEquals(machine.getPublicAddresses(), ImmutableSet.of(nodePublicAddress)); + assertEquals(machine.getPrivateAddresses(), ImmutableSet.of(nodePrivateAddress)); + + // TODO what user/password should we expect? Fails below because has "dummy": +// assertEquals(machine.getUser(), "myuser"); +// assertEquals(machine.config().get(JcloudsLocationConfig.PASSWORD), "mypassword"); + } + + @Test + public void testResolvesHostStringInFlags() throws Exception { + runResolvesHostsInFlags(nodeId); + } + + @Test + public void testResolvesHostsListInFlags() throws Exception { + runResolvesHostsInFlags(ImmutableList.of(nodeId)); + } + + @Test + public void testResolvesHostsListOfMapsInFlags() throws Exception { + runResolvesHostsInFlags(ImmutableList.of(ImmutableMap.of("id", nodeId))); + } + + protected void runResolvesHostsInFlags(Object hostsValInFlags) throws Exception { + String spec = "jcloudsByon:(provider=\""+SOFTLAYER_PROVIDER+"\",region=\""+SOFTLAYER_AMS01_REGION_NAME+"\")"; + Map<?,?> specFlags = ImmutableMap.of( + JcloudsLocationConfig.COMPUTE_SERVICE_REGISTRY, computeServiceRegistry, + "hosts", hostsValInFlags); + + FixedListMachineProvisioningLocation<MachineLocation> location = getLocationManaged(spec, specFlags); + + JcloudsSshMachineLocation machine = (JcloudsSshMachineLocation) Iterables.getOnlyElement(location.getAllMachines()); + assertEquals(machine.getJcloudsId(), nodeId); + } + + @Test + public void testLocationSpecDoesNotCreateMachines() throws Exception { + Collection<Location> before = managementContext.getLocationManager().getLocations(); + String spec = "jcloudsByon:(provider=\""+SOFTLAYER_PROVIDER+"\",region=\""+SOFTLAYER_AMS01_REGION_NAME+"\",user=\"myname\",hosts=\""+nodeId+"\")"; + Map<?,?> specFlags = ImmutableMap.of(JcloudsLocationConfig.COMPUTE_SERVICE_REGISTRY, computeServiceRegistry); + + @SuppressWarnings("unused") + LocationSpec<FixedListMachineProvisioningLocation<MachineLocation>> locationSpec = getLocationSpec(spec, specFlags); + + Collection<Location> after = managementContext.getLocationManager().getLocations(); + assertEquals(after, before, "after="+after+"; before="+before); + } + + @SuppressWarnings("unchecked") + private LocationSpec<FixedListMachineProvisioningLocation<MachineLocation>> getLocationSpec(String val, Map<?,?> specFlags) { + return (LocationSpec<FixedListMachineProvisioningLocation<MachineLocation>>) managementContext.getLocationRegistry().getLocationSpec(val, specFlags).get(); + } + + @SuppressWarnings("unchecked") + private FixedListMachineProvisioningLocation<MachineLocation> getLocationManaged(String val, Map<?,?> specFlags) { + return (FixedListMachineProvisioningLocation<MachineLocation>) managementContext.getLocationRegistry().getLocationManaged(val, specFlags); + } +}