http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/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 new file mode 100644 index 0000000..14e43e5 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/byon/FixedListMachineProvisioningLocation.java @@ -0,0 +1,476 @@ +/* + * 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.byon; + +import static org.apache.brooklyn.util.GroovyJavaMethods.truth; + +import java.io.Closeable; +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +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.api.location.MachineLocationCustomizer; +import org.apache.brooklyn.api.location.MachineProvisioningLocation; +import org.apache.brooklyn.api.location.NoMachinesAvailableException; +import org.apache.brooklyn.api.mgmt.LocationManager; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Function; +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.common.reflect.TypeToken; + +import org.apache.brooklyn.location.cloud.CloudLocationConfig; +import org.apache.brooklyn.location.core.AbstractLocation; +import org.apache.brooklyn.location.ssh.SshMachineLocation; +import org.apache.brooklyn.util.collections.CollectionFunctionals; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.collections.MutableSet; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.core.flags.SetFromFlag; +import org.apache.brooklyn.util.stream.Streams; +import org.apache.brooklyn.util.text.WildcardGlobs; +import org.apache.brooklyn.util.text.WildcardGlobs.PhraseTreatment; + +/** + * A provisioner of {@link MachineLocation}s which takes a list of machines it can connect to. + * The collection of initial machines should be supplied in the 'machines' flag in the constructor, + * for example a list of machines which can be SSH'd to. + * + * This can be extended to have a mechanism to make more machines to be available + * (override provisionMore and canProvisionMore). + */ +public class FixedListMachineProvisioningLocation<T extends MachineLocation> extends AbstractLocation +implements MachineProvisioningLocation<T>, Closeable { + + // TODO Synchronization looks very wrong for accessing machines/inUse + // e.g. removeChild doesn't synchronize when doing machines.remove(...), + // and getMachines() returns the real sets risking + // ConcurrentModificationException in the caller if it iterates over them etc. + + private static final Logger log = LoggerFactory.getLogger(FixedListMachineProvisioningLocation.class); + + public static final ConfigKey<Function<Iterable<? extends MachineLocation>, MachineLocation>> MACHINE_CHOOSER = + ConfigKeys.newConfigKey( + new TypeToken<Function<Iterable<? extends MachineLocation>, MachineLocation>>() {}, + "byon.machineChooser", + "For choosing which of the possible machines is chosen and returned by obtain()", + CollectionFunctionals.<MachineLocation>firstElement()); + + public static final ConfigKey<Collection<MachineLocationCustomizer>> MACHINE_LOCATION_CUSTOMIZERS = CloudLocationConfig.MACHINE_LOCATION_CUSTOMIZERS; + + private final Object lock = new Object(); + + @SetFromFlag + protected Set<T> machines; + + @SetFromFlag + protected Set<T> inUse; + + @SetFromFlag + protected Set<T> pendingRemoval; + + @SetFromFlag + protected Map<T, Map<String, Object>> origConfigs; + + public FixedListMachineProvisioningLocation() { + this(Maps.newLinkedHashMap()); + } + public FixedListMachineProvisioningLocation(Map properties) { + super(properties); + + if (isLegacyConstruction()) { + init(); + } + } + + @Override + public void init() { + super.init(); + + Set<T> machinesCopy = MutableSet.of(); + 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 { + Location parent = location.getParent(); + if (parent == null) { + addChild(location); + } + machinesCopy.add(location); + } + } + if (!machinesCopy.equals(machines)) { + machines = machinesCopy; + } + } + + @Override + public String toVerboseString() { + return Objects.toStringHelper(this).omitNullValues() + .add("id", getId()).add("name", getDisplayName()) + .add("machinesAvailable", getAvailable()).add("machinesInUse", getInUse()) + .toString(); + } + + @Override + public AbstractLocation configure(Map<?,?> properties) { + if (machines == null) machines = Sets.newLinkedHashSet(); + if (inUse == null) inUse = Sets.newLinkedHashSet(); + if (pendingRemoval == null) pendingRemoval = Sets.newLinkedHashSet(); + if (origConfigs == null) origConfigs = Maps.newLinkedHashMap(); + return super.configure(properties); + } + + @SuppressWarnings("unchecked") + public FixedListMachineProvisioningLocation<T> newSubLocation(Map<?,?> newFlags) { + // TODO shouldn't have to copy config bag as it should be inherited (but currently it is not used inherited everywhere; just most places) + return getManagementContext().getLocationManager().createLocation(LocationSpec.create(getClass()) + .parent(this) + .configure(config().getLocalBag().getAllConfig()) // FIXME Should this just be inherited? + .configure(newFlags)); + } + + @Override + public void close() { + for (T machine : machines) { + if (machine instanceof Closeable) Streams.closeQuietly((Closeable)machine); + } + } + + public void addMachine(T machine) { + synchronized (lock) { + if (machines.contains(machine)) { + throw new IllegalArgumentException("Cannot add "+machine+" to "+toString()+", because already contained"); + } + + Location existingParent = ((Location)machine).getParent(); + if (existingParent == null) { + addChild(machine); + } + + machines.add(machine); + } + } + + public void removeMachine(T machine) { + synchronized (lock) { + if (inUse.contains(machine)) { + pendingRemoval.add(machine); + } else { + machines.remove(machine); + pendingRemoval.remove(machine); + if (this.equals(machine.getParent())) { + removeChild((Location)machine); + } + } + } + } + + protected Set<T> getMachines() { + return machines; + } + + public Set<T> getAvailable() { + Set<T> a = Sets.newLinkedHashSet(machines); + a.removeAll(inUse); + return a; + } + + public Set<T> getInUse() { + return Sets.newLinkedHashSet(inUse); + } + + public Set<T> getAllMachines() { + return ImmutableSet.copyOf(machines); + } + + @Override + public void addChild(Location child) { + super.addChild(child); + machines.add((T)child); + } + + @Override + public boolean removeChild(Location child) { + if (inUse.contains(child)) { + throw new IllegalStateException("Child location "+child+" is in use; cannot remove from "+this); + } + machines.remove(child); + return super.removeChild(child); + } + + protected boolean canProvisionMore() { + return false; + } + + protected void provisionMore(int size) { + provisionMore(size, ImmutableMap.of()); + } + + protected void provisionMore(int size, Map<?,?> flags) { + throw new IllegalStateException("more not permitted"); + } + + public T obtain() throws NoMachinesAvailableException { + return obtain(Maps.<String,Object>newLinkedHashMap()); + } + + @Override + public T obtain(Map<?,?> flags) throws NoMachinesAvailableException { + T machine; + T desiredMachine = (T) flags.get("desiredMachine"); + ConfigBag allflags = ConfigBag.newInstanceExtending(config().getBag()).putAll(flags); + Function<Iterable<? extends MachineLocation>, MachineLocation> chooser = allflags.get(MACHINE_CHOOSER); + + synchronized (lock) { + Set<T> a = getAvailable(); + if (a.isEmpty()) { + if (canProvisionMore()) { + provisionMore(1, allflags.getAllConfig()); + a = getAvailable(); + } + if (a.isEmpty()) + throw new NoMachinesAvailableException("No machines available in "+toString()); + } + if (desiredMachine != null) { + if (a.contains(desiredMachine)) { + machine = desiredMachine; + } else { + throw new IllegalStateException("Desired machine "+desiredMachine+" not available in "+toString()+"; "+ + (inUse.contains(desiredMachine) ? "machine in use" : "machine unknown")); + } + } else { + machine = (T) chooser.apply(a); + if (!a.contains(machine)) { + throw new IllegalStateException("Machine chooser attempted to choose '"+machine+"' from outside the available set, in "+this); + } + } + inUse.add(machine); + updateMachineConfig(machine, flags); + } + + for (MachineLocationCustomizer customizer : getMachineCustomizers(allflags)) { + customizer.customize(machine); + } + + return machine; + } + + @Override + public void release(T machine) { + ConfigBag machineConfig = ((ConfigurationSupportInternal)machine.config()).getBag(); + for (MachineLocationCustomizer customizer : getMachineCustomizers(machineConfig)) { + customizer.preRelease(machine); + } + + synchronized (lock) { + if (inUse.contains(machine) == false) + throw new IllegalStateException("Request to release machine "+machine+", but this machine is not currently allocated"); + restoreMachineConfig(machine); + inUse.remove(machine); + + if (pendingRemoval.contains(machine)) { + removeMachine(machine); + } + } + } + + @Override + public Map<String,Object> getProvisioningFlags(Collection<String> tags) { + return Maps.<String,Object>newLinkedHashMap(); + } + + protected void updateMachineConfig(T machine, Map<?, ?> flags) { + if (origConfigs == null) { + // For backwards compatibility, where peristed state did not have this. + origConfigs = Maps.newLinkedHashMap(); + } + Map<String, Object> strFlags = ConfigBag.newInstance(flags).getAllConfig(); + Map<String, Object> origConfig = ((ConfigurationSupportInternal)machine.config()).getLocalBag().getAllConfig(); + origConfigs.put(machine, origConfig); + requestPersist(); + + ((ConfigurationSupportInternal)machine.config()).addToLocalBag(strFlags); + } + + protected void restoreMachineConfig(MachineLocation machine) { + if (origConfigs == null) { + // For backwards compatibility, where peristed state did not have this. + origConfigs = Maps.newLinkedHashMap(); + } + Map<String, Object> origConfig = origConfigs.remove(machine); + if (origConfig == null) return; + requestPersist(); + + Set<String> currentKeys = ((ConfigurationSupportInternal)machine.config()).getLocalBag().getAllConfig().keySet(); + Set<String> newKeys = Sets.difference(currentKeys, origConfig.entrySet()); + for (String key : newKeys) { + ((ConfigurationSupportInternal)machine.config()).removeFromLocalBag(key); + } + ((ConfigurationSupportInternal)machine.config()).addToLocalBag(origConfig); + } + + @SuppressWarnings("unchecked") + private <K> K getConfigPreferringOverridden(ConfigKey<K> key, Map<?,?> overrides) { + K result = (K) overrides.get(key); + if (result == null) result = (K) overrides.get(key.getName()); + if (result == null) result = getConfig(key); + return result; + } + + protected Collection<MachineLocationCustomizer> getMachineCustomizers(ConfigBag setup) { + Collection<MachineLocationCustomizer> customizers = setup.get(MACHINE_LOCATION_CUSTOMIZERS); + return (customizers == null ? ImmutableList.<MachineLocationCustomizer>of() : customizers); + } + + /** + * Facilitates fluent/programmatic style for constructing a fixed pool of machines. + * <pre> + * {@code + * new FixedListMachineProvisioningLocation.Builder() + * .user("alex") + * .keyFile("/Users/alex/.ssh/id_rsa") + * .addAddress("10.0.0.1") + * .addAddress("10.0.0.2") + * .addAddress("10.0.0.3") + * .addAddressMultipleTimes("[email protected]", 5) + * .build(); + * } + * </pre> + */ + public static class Builder { + LocationManager lm; + String user; + String privateKeyPassphrase; + String privateKeyFile; + String privateKeyData; + File localTempDir; + List machines = Lists.newArrayList(); + + public Builder(LocationManager lm) { + this.lm = lm; + } + public Builder user(String user) { + this.user = user; + return this; + } + public Builder keyPassphrase(String keyPassphrase) { + this.privateKeyPassphrase = keyPassphrase; + return this; + } + public Builder keyFile(String keyFile) { + this.privateKeyFile = keyFile; + return this; + } + public Builder keyData(String keyData) { + this.privateKeyData = keyData; + return this; + } + public Builder localTempDir(File val) { + this.localTempDir = val; + return this; + } + /** adds the locations; user and keyfile set in the builder are _not_ applied to the machine + * (use add(String address) for that) + */ + public Builder add(SshMachineLocation location) { + machines.add(location); + return this; + } + public Builder addAddress(String address) { + return addAddresses(address); + } + public Builder addAddressMultipleTimes(String address, int n) { + for (int i=0; i<n; i++) + addAddresses(address); + return this; + } + public Builder addAddresses(String address1, String ...others) { + List<String> addrs = new ArrayList<String>(); + addrs.addAll(WildcardGlobs.getGlobsAfterBraceExpansion("{"+address1+"}", + true /* numeric */, /* no quote support though */ PhraseTreatment.NOT_A_SPECIAL_CHAR, PhraseTreatment.NOT_A_SPECIAL_CHAR)); + for (String address: others) + addrs.addAll(WildcardGlobs.getGlobsAfterBraceExpansion("{"+address+"}", + true /* numeric */, /* no quote support though */ PhraseTreatment.NOT_A_SPECIAL_CHAR, PhraseTreatment.NOT_A_SPECIAL_CHAR)); + for (String addr: addrs) + add(createMachine(addr)); + return this; + } + protected SshMachineLocation createMachine(String addr) { + if (lm==null) + return new SshMachineLocation(makeConfig(addr)); + else + return lm.createLocation(makeConfig(addr), SshMachineLocation.class); + } + private Map makeConfig(String address) { + String user = this.user; + if (address.contains("@")) { + user = address.substring(0, address.indexOf("@")); + address = address.substring(address.indexOf("@")+1); + } + Map config = MutableMap.of("address", address); + if (truth(user)) { + config.put("user", user); + config.put("sshconfig.user", user); + } + if (truth(privateKeyPassphrase)) config.put("sshconfig.privateKeyPassphrase", privateKeyPassphrase); + if (truth(privateKeyFile)) config.put("sshconfig.privateKeyFile", privateKeyFile); + if (truth(privateKeyData)) config.put("sshconfig.privateKey", privateKeyData); + if (truth(localTempDir)) config.put("localTempDir", localTempDir); + return config; + } + @SuppressWarnings("unchecked") + public FixedListMachineProvisioningLocation<SshMachineLocation> build() { + if (lm==null) + return new FixedListMachineProvisioningLocation<SshMachineLocation>(MutableMap.builder() + .putIfNotNull("machines", machines) + .putIfNotNull("user", user) + .putIfNotNull("privateKeyPassphrase", privateKeyPassphrase) + .putIfNotNull("privateKeyFile", privateKeyFile) + .putIfNotNull("privateKeyData", privateKeyData) + .putIfNotNull("localTempDir", localTempDir) + .build()); + else + return lm.createLocation(MutableMap.builder() + .putIfNotNull("machines", machines) + .putIfNotNull("user", user) + .putIfNotNull("privateKeyPassphrase", privateKeyPassphrase) + .putIfNotNull("privateKeyFile", privateKeyFile) + .putIfNotNull("privateKeyData", privateKeyData) + .putIfNotNull("localTempDir", localTempDir) + .build(), + FixedListMachineProvisioningLocation.class); + } + } +}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/byon/HostLocationResolver.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/byon/HostLocationResolver.java b/core/src/main/java/org/apache/brooklyn/location/byon/HostLocationResolver.java new file mode 100644 index 0000000..55263e2 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/byon/HostLocationResolver.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.location.byon; + +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.location.core.AbstractLocationResolver; +import org.apache.brooklyn.location.core.LocationConfigUtils; +import org.apache.brooklyn.location.core.LocationPropertiesFromBrooklynProperties; +import org.apache.brooklyn.location.core.internal.LocationInternal; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.guava.Maybe.Absent; +import org.apache.brooklyn.util.text.KeyValueParser; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; + +public class HostLocationResolver extends AbstractLocationResolver { + + private static final String HOST = "host"; + + @SuppressWarnings("rawtypes") + @Override + public Location newLocationFromString(Map locationFlags, String spec, LocationRegistry registry) { + // Extract args from spec + ParsedSpec parsedSpec = specParser.parse(spec); + Map<String, String> argsMap = parsedSpec.argsMap; + if (argsMap.isEmpty()) { + throw new IllegalArgumentException("Invalid host spec (no host supplied): "+spec); + } else if (argsMap.size() == 1 && Iterables.get(argsMap.values(), 0) == null) { + // only given ip or hostname + argsMap = ImmutableMap.of("hosts", Iterables.get(argsMap.keySet(), 0)); + } else if (!(argsMap.containsKey("host") || argsMap.containsKey("hosts"))) { + throw new IllegalArgumentException("Invalid host spec (no host supplied): "+spec); + } + + // Find generic applicable properties + Map globalProperties = registry.getProperties(); + String namedLocation = (String) locationFlags.get(LocationInternal.NAMED_SPEC_NAME.getName()); + Map<String, Object> filteredProperties = new LocationPropertiesFromBrooklynProperties().getLocationProperties(null, namedLocation, globalProperties); + ConfigBag flags = ConfigBag.newInstance(locationFlags).putIfAbsent(filteredProperties); + flags.remove(LocationInternal.NAMED_SPEC_NAME); + + // Generate target spec + String target = "byon("+KeyValueParser.toLine(argsMap)+")"; + Maybe<Location> testResolve = managementContext.getLocationRegistry().resolve(target, false, null); + if (!testResolve.isPresent()) { + throw new IllegalArgumentException("Invalid target location '" + target + "' for location '"+HOST+"': "+ + Exceptions.collapseText( ((Absent<?>)testResolve).getException() ), ((Absent<?>)testResolve).getException()); + } + + return managementContext.getLocationManager().createLocation(LocationSpec.create(SingleMachineProvisioningLocation.class) + .configure("location", target) + .configure("locationFlags", flags.getAllConfig()) + .configure(LocationConfigUtils.finalAndOriginalSpecs(spec, locationFlags, globalProperties, namedLocation))); + } + + @Override + public String getPrefix() { + return HOST; + } + + @Override + protected Class<? extends Location> getLocationType() { + return SingleMachineProvisioningLocation.class; + } + + @Override + protected SpecParser getSpecParser() { + return new SpecParser(getPrefix()).setExampleUsage("\"host(1.1.1.1)\" or \"host(host=1.1.1.1,name=myname)\""); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/byon/SingleMachineLocationResolver.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/byon/SingleMachineLocationResolver.java b/core/src/main/java/org/apache/brooklyn/location/byon/SingleMachineLocationResolver.java new file mode 100644 index 0000000..6265f5c --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/byon/SingleMachineLocationResolver.java @@ -0,0 +1,81 @@ +/* + * 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.byon; + +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.location.core.AbstractLocationResolver; +import org.apache.brooklyn.location.core.LocationConfigUtils; +import org.apache.brooklyn.location.core.LocationPropertiesFromBrooklynProperties; +import org.apache.brooklyn.location.core.internal.LocationInternal; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.guava.Maybe.Absent; + +public class SingleMachineLocationResolver extends AbstractLocationResolver { + + private static final String SINGLE = "single"; + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @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()); + + if (registry != null) { + LocationPropertiesFromBrooklynProperties.setLocalTempDir(globalProperties, config); + } + + if (config.getStringKey("target") == null) { + throw new IllegalArgumentException("target must be specified in single-machine spec"); + } + String target = config.getStringKey("target").toString(); + config.remove("target"); + Maybe<Location> testResolve = managementContext.getLocationRegistry().resolve(target, false, null); + if (!testResolve.isPresent()) { + throw new IllegalArgumentException("Invalid target location '" + target + "' for location '"+SINGLE+"': "+ + Exceptions.collapseText( ((Absent<?>)testResolve).getException() )); + } + + return managementContext.getLocationManager().createLocation(LocationSpec.create(SingleMachineProvisioningLocation.class) + .configure("location", target) + .configure("locationFlags", config.getAllConfig()) + .configure(LocationConfigUtils.finalAndOriginalSpecs(spec, locationFlags, globalProperties, namedLocation))); + } + + @Override + public String getPrefix() { + return SINGLE; + } + + @Override + protected Class<? extends Location> getLocationType() { + return SingleMachineProvisioningLocation.class; + } + + @Override + protected SpecParser getSpecParser() { + return new SpecParser(getPrefix()).setExampleUsage("\"single(target=jclouds:aws-ec2:us-east-1)\""); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/byon/SingleMachineProvisioningLocation.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/byon/SingleMachineProvisioningLocation.java b/core/src/main/java/org/apache/brooklyn/location/byon/SingleMachineProvisioningLocation.java new file mode 100644 index 0000000..3338da5 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/byon/SingleMachineProvisioningLocation.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.location.byon; + +import java.util.Map; + +import org.apache.brooklyn.api.location.MachineLocation; +import org.apache.brooklyn.api.location.MachineProvisioningLocation; +import org.apache.brooklyn.api.location.NoMachinesAvailableException; +import org.apache.brooklyn.util.core.flags.SetFromFlag; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableMap; + +public class SingleMachineProvisioningLocation<T extends MachineLocation> extends FixedListMachineProvisioningLocation<T> { + private static final long serialVersionUID = -4216528515792151062L; + + private static final Logger log = LoggerFactory.getLogger(SingleMachineProvisioningLocation.class); + + @SetFromFlag(nullable=false) + private String location; + + @SetFromFlag(nullable=false) + private Map<?,?> locationFlags; + + private T singleLocation; + private int referenceCount; + private MachineProvisioningLocation<T> provisioningLocation; + + + public SingleMachineProvisioningLocation() { + } + + @SuppressWarnings("rawtypes") + public SingleMachineProvisioningLocation(String location, Map locationFlags) { + this.locationFlags = locationFlags; + this.location = location; + } + + @SuppressWarnings("rawtypes") + @Override + public synchronized T obtain(Map flags) throws NoMachinesAvailableException { + log.info("Flags {} passed to newLocationFromString will be ignored, using {}", flags, locationFlags); + return obtain(); + } + + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public synchronized T obtain() throws NoMachinesAvailableException { + if (singleLocation == null) { + if (provisioningLocation == null) { + provisioningLocation = (MachineProvisioningLocation) getManagementContext().getLocationRegistry().resolve( + location, locationFlags); + } + singleLocation = provisioningLocation.obtain(ImmutableMap.of()); + inUse.add(singleLocation); + } + referenceCount++; + return singleLocation; + } + + @Override + public synchronized void release(T machine) { + if (!machine.equals(singleLocation)) { + throw new IllegalArgumentException("Invalid machine " + machine + " passed to release, expecting: " + singleLocation); + } + if (--referenceCount == 0) { + provisioningLocation.release(machine); + singleLocation = null; + } + inUse.remove(machine); + }; + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/cloud/AbstractCloudMachineProvisioningLocation.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/cloud/AbstractCloudMachineProvisioningLocation.java b/core/src/main/java/org/apache/brooklyn/location/cloud/AbstractCloudMachineProvisioningLocation.java index d09cadc..6a7dc73 100644 --- a/core/src/main/java/org/apache/brooklyn/location/cloud/AbstractCloudMachineProvisioningLocation.java +++ b/core/src/main/java/org/apache/brooklyn/location/cloud/AbstractCloudMachineProvisioningLocation.java @@ -24,7 +24,7 @@ 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.location.basic.AbstractLocation; +import org.apache.brooklyn.location.core.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; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/cloud/AvailabilityZoneExtension.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/cloud/AvailabilityZoneExtension.java b/core/src/main/java/org/apache/brooklyn/location/cloud/AvailabilityZoneExtension.java index 312cb83..c0143ae 100644 --- a/core/src/main/java/org/apache/brooklyn/location/cloud/AvailabilityZoneExtension.java +++ b/core/src/main/java/org/apache/brooklyn/location/cloud/AvailabilityZoneExtension.java @@ -22,7 +22,7 @@ import java.util.List; import org.apache.brooklyn.api.location.Location; import org.apache.brooklyn.entity.group.DynamicCluster; -import org.apache.brooklyn.location.basic.MultiLocation; +import org.apache.brooklyn.location.core.MultiLocation; import com.google.common.annotations.Beta; import com.google.common.base.Predicate; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/cloud/CloudLocationConfig.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/cloud/CloudLocationConfig.java b/core/src/main/java/org/apache/brooklyn/location/cloud/CloudLocationConfig.java index 2bb2d7e..935a96c 100644 --- a/core/src/main/java/org/apache/brooklyn/location/cloud/CloudLocationConfig.java +++ b/core/src/main/java/org/apache/brooklyn/location/cloud/CloudLocationConfig.java @@ -27,7 +27,7 @@ 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.location.basic.LocationConfigKeys; +import org.apache.brooklyn.location.core.LocationConfigKeys; import org.apache.brooklyn.util.core.flags.SetFromFlag; public interface CloudLocationConfig { http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/core/AbstractLocation.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/core/AbstractLocation.java b/core/src/main/java/org/apache/brooklyn/location/core/AbstractLocation.java new file mode 100644 index 0000000..026cf87 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/core/AbstractLocation.java @@ -0,0 +1,709 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.location.core; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.brooklyn.util.GroovyJavaMethods.elvis; +import static org.apache.brooklyn.util.JavaGroovyEquivalents.groovyTruth; + +import java.io.Closeable; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.location.LocationSpec; +import org.apache.brooklyn.api.mgmt.Task; +import org.apache.brooklyn.api.mgmt.rebind.RebindSupport; +import org.apache.brooklyn.api.mgmt.rebind.mementos.LocationMemento; +import org.apache.brooklyn.api.objs.Configurable; +import org.apache.brooklyn.config.ConfigInheritance; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.config.ConfigKey.HasConfigKey; +import org.apache.brooklyn.core.config.BasicConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.internal.BrooklynFeatureEnablement; +import org.apache.brooklyn.core.internal.storage.BrooklynStorage; +import org.apache.brooklyn.core.internal.storage.Reference; +import org.apache.brooklyn.core.internal.storage.impl.BasicReference; +import org.apache.brooklyn.core.mgmt.internal.LocalLocationManager; +import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; +import org.apache.brooklyn.core.mgmt.rebind.BasicLocationRebindSupport; +import org.apache.brooklyn.core.objs.AbstractBrooklynObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.brooklyn.location.core.internal.LocationDynamicType; +import org.apache.brooklyn.location.core.internal.LocationInternal; +import org.apache.brooklyn.location.geo.HasHostGeoInfo; +import org.apache.brooklyn.location.geo.HostGeoInfo; +import org.apache.brooklyn.util.collections.SetFromLiveMap; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.core.flags.FlagUtils; +import org.apache.brooklyn.util.core.flags.TypeCoercions; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.stream.Streams; + +import com.google.common.base.Objects; +import com.google.common.base.Objects.ToStringHelper; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.common.reflect.TypeToken; + +/** + * A basic implementation of the {@link Location} interface. + * + * This provides an implementation which works according to the requirements of + * the interface documentation, and is ready to be extended to make more specialized locations. + * + * Override {@link #configure(Map)} to add special initialization logic. + */ +public abstract class AbstractLocation extends AbstractBrooklynObject implements LocationInternal, HasHostGeoInfo, Configurable { + + private static final long serialVersionUID = -7495805474138619830L; + + /** @deprecated since 0.7.0 shouldn't be public */ + @Deprecated + public static final Logger LOG = LoggerFactory.getLogger(AbstractLocation.class); + + public static final ConfigKey<Location> PARENT_LOCATION = new BasicConfigKey<Location>(Location.class, "parentLocation"); + + public static final ConfigKey<Boolean> TEMPORARY_LOCATION = ConfigKeys.newBooleanConfigKey("temporaryLocation", + "Indicates that the location is a temporary location that has been created to test connectivity, and that" + + "the location's events should not be recorded by usage listeners", false); + + private final AtomicBoolean configured = new AtomicBoolean(); + + private Reference<Long> creationTimeUtc = new BasicReference<Long>(System.currentTimeMillis()); + + // _not_ set from flag; configured explicitly in configure, because we also need to update the parent's list of children + private Reference<Location> parent = new BasicReference<Location>(); + + // NB: all accesses should be synchronized + private Set<Location> children = Sets.newLinkedHashSet(); + + private Reference<String> name = new BasicReference<String>(); + private boolean displayNameAutoGenerated = true; + + private Reference<HostGeoInfo> hostGeoInfo = new BasicReference<HostGeoInfo>(); + + private BasicConfigurationSupport config = new BasicConfigurationSupport(); + + private ConfigBag configBag = new ConfigBag(); + + private volatile boolean managed; + + private boolean inConstruction; + + private Reference<Map<Class<?>, Object>> extensions = new BasicReference<Map<Class<?>, Object>>(Maps.<Class<?>, Object>newConcurrentMap()); + + private final LocationDynamicType locationType; + + /** + * Construct a new instance of an AbstractLocation. + */ + public AbstractLocation() { + this(Maps.newLinkedHashMap()); + } + + /** + * Construct a new instance of an AbstractLocation. + * + * The properties map recognizes the following keys: + * <ul> + * <li>name - a name for the location + * <li>parentLocation - the parent {@link Location} + * </ul> + * + * Other common properties (retrieved via get/findLocationProperty) include: + * <ul> + * <li>latitude + * <li>longitude + * <li>displayName + * <li>iso3166 - list of iso3166-2 code strings + * <li>timeZone + * <li>abbreviatedName + * </ul> + */ + public AbstractLocation(Map<?,?> properties) { + super(properties); + inConstruction = true; + + // When one calls getConfig(key), we want to use the default value specified on *this* location + // if it overrides the default config, by using the type object + locationType = new LocationDynamicType(this); + + if (isLegacyConstruction()) { + AbstractBrooklynObject checkWeGetThis = configure(properties); + assert this.equals(checkWeGetThis) : this+" configure method does not return itself; returns "+checkWeGetThis+" instead of "+this; + + boolean deferConstructionChecks = (properties.containsKey("deferConstructionChecks") && TypeCoercions.coerce(properties.get("deferConstructionChecks"), Boolean.class)); + if (!deferConstructionChecks) { + FlagUtils.checkRequiredFields(this); + } + } + + inConstruction = false; + } + + protected void assertNotYetManaged() { + if (!inConstruction && Locations.isManaged(this)) { + LOG.warn("Configuration being made to {} after deployment; may not be supported in future versions", this); + } + //throw new IllegalStateException("Cannot set configuration "+key+" on active location "+this) + } + + public void setManagementContext(ManagementContextInternal managementContext) { + super.setManagementContext(managementContext); + if (displayNameAutoGenerated && getId() != null) name.set(getClass().getSimpleName()+":"+getId().substring(0, Math.min(getId().length(),4))); + + if (BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_USE_BROOKLYN_LIVE_OBJECTS_DATAGRID_STORAGE)) { + Location oldParent = parent.get(); + Set<Location> oldChildren = children; + Map<String, Object> oldConfig = configBag.getAllConfig(); + Long oldCreationTimeUtc = creationTimeUtc.get(); + String oldDisplayName = name.get(); + HostGeoInfo oldHostGeoInfo = hostGeoInfo.get(); + + parent = managementContext.getStorage().getReference(getId()+"-parent"); + children = SetFromLiveMap.create(managementContext.getStorage().<Location,Boolean>getMap(getId()+"-children")); + creationTimeUtc = managementContext.getStorage().getReference(getId()+"-creationTime"); + hostGeoInfo = managementContext.getStorage().getReference(getId()+"-hostGeoInfo"); + name = managementContext.getStorage().getReference(getId()+"-displayName"); + + // Only override stored defaults if we have actual values. We might be in setManagementContext + // because we are reconstituting an existing entity in a new brooklyn management-node (in which + // case believe what is already in the storage), or we might be in the middle of creating a new + // entity. Normally for a new entity (using EntitySpec creation approach), this will get called + // before setting the parent etc. However, for backwards compatibility we still support some + // things calling the entity's constructor directly. + if (oldParent != null) parent.set(oldParent); + if (oldChildren.size() > 0) children.addAll(oldChildren); + if (creationTimeUtc.isNull()) creationTimeUtc.set(oldCreationTimeUtc); + if (hostGeoInfo.isNull()) hostGeoInfo.set(oldHostGeoInfo); + if (name.isNull()) { + name.set(oldDisplayName); + } else { + displayNameAutoGenerated = false; + } + + configBag = ConfigBag.newLiveInstance(managementContext.getStorage().<String,Object>getMap(getId()+"-config")); + if (oldConfig.size() > 0) { + configBag.putAll(oldConfig); + } + } + } + + /** + * @deprecated since 0.7.0; only used for legacy brooklyn types where constructor is called directly; + * see overridden method for more info + */ + @SuppressWarnings("serial") + @Override + @Deprecated + public AbstractLocation configure(Map<?,?> properties) { + assertNotYetManaged(); + + boolean firstTime = !configured.getAndSet(true); + + configBag.putAll(properties); + + if (properties.containsKey(PARENT_LOCATION.getName())) { + // need to ensure parent's list of children is also updated + setParent(configBag.get(PARENT_LOCATION)); + + // don't include parentLocation in configBag, as breaks rebind + configBag.remove(PARENT_LOCATION); + } + + // NB: flag-setting done here must also be done in BasicLocationRebindSupport + FlagUtils.setFieldsFromFlagsWithBag(this, properties, configBag, firstTime); + FlagUtils.setAllConfigKeys(this, configBag, false); + + if (properties.containsKey("displayName")) { + name.set((String) removeIfPossible(properties, "displayName")); + displayNameAutoGenerated = false; + } else if (properties.containsKey("name")) { + name.set((String) removeIfPossible(properties, "name")); + displayNameAutoGenerated = false; + } else if (isLegacyConstruction()) { + name.set(getClass().getSimpleName()+":"+getId().substring(0, Math.min(getId().length(),4))); + displayNameAutoGenerated = true; + } + + // TODO Explicitly dealing with iso3166 here because want custom splitter rule comma-separated string. + // Is there a better way to do it (e.g. more similar to latitude, where configKey+TypeCoercion is enough)? + if (groovyTruth(properties.get("iso3166"))) { + Object rawCodes = removeIfPossible(properties, "iso3166"); + Set<String> codes; + if (rawCodes instanceof CharSequence) { + codes = ImmutableSet.copyOf(Splitter.on(",").trimResults().split((CharSequence)rawCodes)); + } else { + codes = TypeCoercions.coerce(rawCodes, new TypeToken<Set<String>>() {}); + } + configBag.put(LocationConfigKeys.ISO_3166, codes); + } + + return this; + } + + // TODO ensure no callers rely on 'remove' semantics, and don't remove; + // or perhaps better use a config bag so we know what is used v unused + private static Object removeIfPossible(Map<?,?> map, Object key) { + try { + return map.remove(key); + } catch (Exception e) { + return map.get(key); + } + } + + public boolean isManaged() { + return getManagementContext() != null && managed; + } + + public void onManagementStarted() { + if (displayNameAutoGenerated) name.set(getClass().getSimpleName()+":"+getId().substring(0, Math.min(getId().length(),4))); + this.managed = true; + } + + public void onManagementStopped() { + this.managed = false; + if (getManagementContext().isRunning()) { + BrooklynStorage storage = ((ManagementContextInternal)getManagementContext()).getStorage(); + storage.remove(getId()+"-parent"); + storage.remove(getId()+"-children"); + storage.remove(getId()+"-creationTime"); + storage.remove(getId()+"-hostGeoInfo"); + storage.remove(getId()+"-displayName"); + storage.remove(getId()+"-config"); + } + } + + @Override + public String getDisplayName() { + return name.get(); + } + + protected boolean isDisplayNameAutoGenerated() { + return displayNameAutoGenerated; + } + + @Override + public Location getParent() { + return parent.get(); + } + + @Override + public Collection<Location> getChildren() { + synchronized (children) { + return ImmutableList.copyOf(children); + } + } + + @Override + public void setParent(Location newParent) { + setParent(newParent, true); + } + + public void setParent(Location newParent, boolean updateChildListParents) { + if (newParent == this) { + throw new IllegalArgumentException("Location cannot be its own parent: "+this); + } + if (newParent == parent.get()) { + return; // no-op; already have desired parent + } + + if (parent.get() != null) { + Location oldParent = parent.get(); + parent.set(null); + if (updateChildListParents) + ((AbstractLocation)oldParent).removeChild(this); + } + // TODO Should we support a location changing parent? The resulting unmanage/manage might cause problems. + // The code above suggests we do, but maybe we should warn or throw error, or at least test it! + + parent.set(newParent); + if (newParent != null) { + if (updateChildListParents) + ((AbstractLocation)newParent).addChild(this); + } + + onChanged(); + } + + @Override + public ConfigurationSupportInternal config() { + return config ; + } + + private class BasicConfigurationSupport implements ConfigurationSupportInternal { + + @Override + public <T> T get(ConfigKey<T> key) { + if (hasConfig(key, false)) return getLocalBag().get(key); + if (getParent() != null && isInherited(key)) { + return getParent().getConfig(key); + } + + // In case this entity class has overridden the given key (e.g. to set default), then retrieve this entity's key + // TODO when locations become entities, the duplication of this compared to EntityConfigMap.getConfig will disappear. + @SuppressWarnings("unchecked") + ConfigKey<T> ownKey = (ConfigKey<T>) elvis(locationType.getConfigKey(key.getName()), key); + + return ownKey.getDefaultValue(); + } + + @Override + public <T> T get(HasConfigKey<T> key) { + return get(key.getConfigKey()); + } + + @Override + public <T> T set(ConfigKey<T> key, T val) { + T result = configBag.put(key, val); + onChanged(); + return result; + } + + @Override + public <T> T set(HasConfigKey<T> key, T val) { + return set(key.getConfigKey(), val); + } + + @Override + public <T> T set(ConfigKey<T> key, Task<T> val) { + // TODO Support for locations + throw new UnsupportedOperationException(); + } + + @Override + public <T> T set(HasConfigKey<T> key, Task<T> val) { + // TODO Support for locations + throw new UnsupportedOperationException(); + } + + @Override + public ConfigBag getBag() { + ConfigBag result = ConfigBag.newInstanceExtending(configBag, ImmutableMap.of()); + Location p = getParent(); + if (p!=null) result.putIfAbsent(((LocationInternal)p).config().getBag()); + return result; + } + + @Override + public ConfigBag getLocalBag() { + return configBag; + } + + @Override + public Maybe<Object> getRaw(ConfigKey<?> key) { + if (hasConfig(key, false)) return Maybe.of(getLocalBag().getStringKey(key.getName())); + if (getParent() != null && isInherited(key)) return ((LocationInternal)getParent()).config().getRaw(key); + return Maybe.absent(); + } + + @Override + public Maybe<Object> getRaw(HasConfigKey<?> key) { + return getRaw(key.getConfigKey()); + } + + @Override + public Maybe<Object> getLocalRaw(ConfigKey<?> key) { + if (hasConfig(key, false)) return Maybe.of(getLocalBag().getStringKey(key.getName())); + return Maybe.absent(); + } + + @Override + public Maybe<Object> getLocalRaw(HasConfigKey<?> key) { + return getLocalRaw(key.getConfigKey()); + } + + @Override + public void addToLocalBag(Map<String, ?> vals) { + configBag.putAll(vals); + } + + @Override + public void removeFromLocalBag(String key) { + configBag.remove(key); + } + + @Override + public void refreshInheritedConfig() { + // no-op for location + } + + @Override + public void refreshInheritedConfigOfChildren() { + // no-op for location + } + + private boolean hasConfig(ConfigKey<?> key, boolean includeInherited) { + if (includeInherited && isInherited(key)) { + return getBag().containsKey(key); + } else { + return getLocalBag().containsKey(key); + } + } + + private boolean isInherited(ConfigKey<?> key) { + ConfigInheritance inheritance = key.getInheritance(); + if (inheritance==null) inheritance = getDefaultInheritance(); + return inheritance.isInherited(key, getParent(), AbstractLocation.this); + } + + private ConfigInheritance getDefaultInheritance() { + return ConfigInheritance.ALWAYS; + } + } + + @Override + public <T> T getConfig(HasConfigKey<T> key) { + return config().get(key); + } + + @Override + public <T> T getConfig(ConfigKey<T> key) { + return config().get(key); + } + + @Override + @Deprecated + public boolean hasConfig(ConfigKey<?> key, boolean includeInherited) { + return config.hasConfig(key, includeInherited); + } + + @Override + @Deprecated + public Map<String,Object> getAllConfig(boolean includeInherited) { + // TODO Have no information about what to include/exclude inheritance wise. + // however few things use getAllConfigBag() + ConfigBag bag = (includeInherited ? config().getBag() : config().getLocalBag()); + return bag.getAllConfig(); + } + + @Override + @Deprecated + public ConfigBag getAllConfigBag() { + // TODO see comments in EntityConfigMap and on interface methods. + // here ConfigBag is used exclusively so + // we have no information about what to include/exclude inheritance wise. + // however few things use getAllConfigBag() + return config().getBag(); + } + + @Override + public ConfigBag getLocalConfigBag() { + return config().getLocalBag(); + } + + /** + * @deprecated since 0.7; use {@link #getLocalConfigBag()} + * @since 0.6 + */ + @Deprecated + public ConfigBag getRawLocalConfigBag() { + return config().getLocalBag(); + } + + @Override + @Deprecated + public <T> T setConfig(ConfigKey<T> key, T value) { + return config().set(key, value); + } + + /** + * @since 0.6.0 (?) - use getDisplayName + * @deprecated since 0.7.0; use {@link #getDisplayName()} + */ + @Deprecated + public void setName(String newName) { + setDisplayName(newName); + } + + public void setDisplayName(String newName) { + name.set(newName); + displayNameAutoGenerated = false; + onChanged(); + } + + @Override + public boolean equals(Object o) { + if (! (o instanceof Location)) { + return false; + } + + Location l = (Location) o; + return getId().equals(l.getId()); + } + + @Override + public int hashCode() { + return getId().hashCode(); + } + + @Override + public boolean containsLocation(Location potentialDescendent) { + Location loc = potentialDescendent; + while (loc != null) { + if (this == loc) return true; + loc = loc.getParent(); + } + return false; + } + + protected <T extends Location> T addChild(LocationSpec<T> spec) { + T child = getManagementContext().getLocationManager().createLocation(spec); + addChild(child); + return child; + } + + @SuppressWarnings("deprecation") + public void addChild(Location child) { + // Previously, setParent delegated to addChildLocation and we sometimes ended up with + // duplicate entries here. Instead this now uses a similar scheme to + // AbstractLocation.setParent/addChild (with any weaknesses for distribution that such a + // scheme might have...). + // + // We continue to use a list to allow identical-looking locations, but they must be different + // instances. + + synchronized (children) { + for (Location contender : children) { + if (contender == child) { + // don't re-add; no-op + return; + } + } + + children.add(child); + } + + if (isManaged()) { + if (!getManagementContext().getLocationManager().isManaged(child)) { + Locations.manage(child, getManagementContext()); + } + } else if (getManagementContext() != null) { + if (((LocalLocationManager)getManagementContext().getLocationManager()).getLocationEvenIfPreManaged(child.getId()) == null) { + ((ManagementContextInternal)getManagementContext()).prePreManage(child); + } + } + + children.add(child); + child.setParent(this); + + onChanged(); + } + + public boolean removeChild(Location child) { + boolean removed; + synchronized (children) { + removed = children.remove(child); + } + if (removed) { + if (child instanceof Closeable) { + Streams.closeQuietly((Closeable)child); + } + child.setParent(null); + + if (isManaged()) { + getManagementContext().getLocationManager().unmanage(child); + } + } + onChanged(); + return removed; + } + + protected void onChanged() { + // currently changes simply trigger re-persistence; there is no intermediate listener as we do for EntityChangeListener + if (isManaged()) { + getManagementContext().getRebindManager().getChangeListener().onChanged(this); + } + } + + /** Default String representation is simplified name of class, together with selected fields. */ + @Override + public String toString() { + return string().toString(); + } + + @Override + public String toVerboseString() { + return toString(); + } + + /** override this, adding to the returned value, to supply additional fields to include in the toString */ + protected ToStringHelper string() { + return Objects.toStringHelper(getClass()).add("id", getId()).add("name", name); + } + + @Override + public HostGeoInfo getHostGeoInfo() { return hostGeoInfo.get(); } + + public void setHostGeoInfo(HostGeoInfo hostGeoInfo) { + if (hostGeoInfo!=null) { + this.hostGeoInfo.set(hostGeoInfo); + setConfig(LocationConfigKeys.LATITUDE, hostGeoInfo.latitude); + setConfig(LocationConfigKeys.LONGITUDE, hostGeoInfo.longitude); + } + } + + @Override + public RebindSupport<LocationMemento> getRebindSupport() { + return new BasicLocationRebindSupport(this); + } + + @Override + public boolean hasExtension(Class<?> extensionType) { + return extensions.get().containsKey(checkNotNull(extensionType, "extensionType")); + } + + @Override + @SuppressWarnings("unchecked") + public <T> T getExtension(Class<T> extensionType) { + Object extension = extensions.get().get(checkNotNull(extensionType, "extensionType")); + if (extension == null) { + throw new IllegalArgumentException("No extension of type "+extensionType+" registered for location "+this); + } + return (T) extension; + } + + @Override + public <T> void addExtension(Class<T> extensionType, T extension) { + checkNotNull(extensionType, "extensionType"); + checkNotNull(extension, "extension"); + checkArgument(extensionType.isInstance(extension), "extension %s does not implement %s", extension, extensionType); + extensions.get().put(extensionType, extension); + } + + @Override + public Map<String, String> toMetadataRecord() { + ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); + if (getDisplayName() != null) builder.put("displayName", getDisplayName()); + if (getParent() != null && getParent().getDisplayName() != null) { + builder.put("parentDisplayName", getParent().getDisplayName()); + } + return builder.build(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/core/AbstractLocationResolver.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/core/AbstractLocationResolver.java b/core/src/main/java/org/apache/brooklyn/location/core/AbstractLocationResolver.java new file mode 100644 index 0000000..c7e80bc --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/core/AbstractLocationResolver.java @@ -0,0 +1,188 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.location.core; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Collections; +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.mgmt.ManagementContext; +import org.apache.brooklyn.location.core.internal.LocationInternal; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.text.KeyValueParser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableList; + +/** + * Examples of valid specs: + * <ul> + * <li>byon(hosts=myhost) + * <li>byon(hosts=myhost,myhost2) + * <li>byon(hosts="myhost, myhost2") + * <li>byon(hosts=myhost,myhost2, name=abc) + * <li>byon(hosts="myhost, myhost2", name="my location name") + * </ul> + * + * @author aled + */ +@SuppressWarnings({"unchecked","rawtypes"}) +public abstract class AbstractLocationResolver implements LocationResolver { + + public static final Logger log = LoggerFactory.getLogger(AbstractLocationResolver.class); + + protected volatile ManagementContext managementContext; + + protected volatile SpecParser specParser; + + protected abstract Class<? extends Location> getLocationType(); + + protected abstract SpecParser getSpecParser(); + + @Override + public void init(ManagementContext managementContext) { + this.managementContext = checkNotNull(managementContext, "managementContext"); + this.specParser = getSpecParser(); + } + + @Override + public boolean accepts(String spec, LocationRegistry registry) { + return BasicLocationRegistry.isResolverPrefixForSpec(this, spec, true); + } + + @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()); + + if (registry != null) { + LocationPropertiesFromBrooklynProperties.setLocalTempDir(globalProperties, config); + } + + return managementContext.getLocationManager().createLocation(LocationSpec.create(getLocationType()) + .configure(config.getAllConfig()) + .configure(LocationConfigUtils.finalAndOriginalSpecs(spec, locationFlags, globalProperties, namedLocation))); + } + + protected ConfigBag extractConfig(Map<?,?> locationFlags, String spec, LocationRegistry registry) { + Map globalProperties = registry.getProperties(); + ParsedSpec parsedSpec = specParser.parse(spec); + String namedLocation = (String) locationFlags.get(LocationInternal.NAMED_SPEC_NAME.getName()); + + // prefer args map over location flags + Map<String, Object> filteredProperties = getFilteredLocationProperties(getPrefix(), namedLocation, globalProperties); + ConfigBag flags = ConfigBag.newInstance(parsedSpec.argsMap).putIfAbsent(locationFlags).putIfAbsent(filteredProperties); + + return flags; + } + + protected Map<String, Object> getFilteredLocationProperties(String provider, String namedLocation, Map<String, ?> globalProperties) { + return new LocationPropertiesFromBrooklynProperties().getLocationProperties(getPrefix(), namedLocation, globalProperties); + } + + protected static class ParsedSpec { + public final String spec; + public final List<String> partsList; + public final Map<String,String> argsMap; + + ParsedSpec(String spec, List<String> partsList, Map<String,String> argsMap) { + this.spec = spec; + this.partsList = ImmutableList.copyOf(partsList); + this.argsMap = Collections.unmodifiableMap(MutableMap.copyOf(argsMap)); + } + } + + /** + * Parses a spec, by default of the general form "prefix:parts1:part2(arg1=val1,arg2=val2)" + */ + protected static class SpecParser { + + protected final String prefix; + protected final Pattern pattern; + private String exampleUsage; + + public SpecParser(String prefix) { + this.prefix = prefix; + pattern = Pattern.compile("("+prefix+"|"+prefix.toLowerCase()+"|"+prefix.toUpperCase()+")" + "(:)?" + "(\\((.*)\\))?$"); + } + + public SpecParser(String prefix, Pattern pattern) { + this.prefix = prefix; + this.pattern = pattern; + } + + public SpecParser setExampleUsage(String exampleUsage) { + this.exampleUsage = exampleUsage; + return this; + } + + protected String getUsage(String spec) { + if (exampleUsage == null) { + return "Spec should be in the form "+pattern; + } else { + return "for example, "+exampleUsage; + } + } + + protected void checkParsedSpec(ParsedSpec parsedSpec) { + // If someone tries "byon:(),byon:()" as a single spec, we get weird key-values! + for (String key : parsedSpec.argsMap.keySet()) { + if (key.contains(":") || key.contains("{") || key.contains("}") || key.contains("(") || key.contains(")")) { + throw new IllegalArgumentException("Invalid byon spec: "+parsedSpec.spec+" (key="+key+")"); + } + } + String name = parsedSpec.argsMap.get("name"); + if (parsedSpec.argsMap.containsKey("name") && (name == null || name.isEmpty())) { + throw new IllegalArgumentException("Invalid location '"+parsedSpec.spec+"'; if name supplied then value must be non-empty"); + } + String displayName = parsedSpec.argsMap.get("displayName"); + if (parsedSpec.argsMap.containsKey("displayName") && (displayName == null || displayName.isEmpty())) { + throw new IllegalArgumentException("Invalid location '"+parsedSpec.spec+"'; if displayName supplied then value must be non-empty"); + } + } + + public ParsedSpec parse(String spec) { + Matcher matcher = pattern.matcher(spec); + if (!matcher.matches()) { + throw new IllegalArgumentException("Invalid location '"+spec+"'; "+getUsage(spec)); + } + + String argsPart = matcher.group(3); + if (argsPart != null && argsPart.startsWith("(") && argsPart.endsWith(")")) { + // TODO Hacky; hosts("1.1.1.1") returns argsPart=("1.1.1.1") + argsPart = argsPart.substring(1, argsPart.length()-1); + } + Map<String, String> argsMap = KeyValueParser.parseMap(argsPart); + ParsedSpec result = new ParsedSpec(spec, ImmutableList.<String>of(), argsMap); + checkParsedSpec(result); + return result; + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/core/AggregatingMachineProvisioningLocation.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/core/AggregatingMachineProvisioningLocation.java b/core/src/main/java/org/apache/brooklyn/location/core/AggregatingMachineProvisioningLocation.java new file mode 100644 index 0000000..6ea9ec9 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/core/AggregatingMachineProvisioningLocation.java @@ -0,0 +1,141 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.location.core; + +import static com.google.common.base.Preconditions.checkState; + +import java.io.Closeable; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.brooklyn.api.location.MachineLocation; +import org.apache.brooklyn.api.location.MachineProvisioningLocation; +import org.apache.brooklyn.api.location.NoMachinesAvailableException; +import org.apache.brooklyn.util.core.flags.SetFromFlag; +import org.apache.brooklyn.util.stream.Streams; + +import com.google.common.base.Objects; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +/** + * Takes a list of other provisioners, and round-robins across them when obtaining a machine. + */ +public class AggregatingMachineProvisioningLocation<T extends MachineLocation> extends AbstractLocation + implements MachineProvisioningLocation<T>, Closeable { + + private static final long serialVersionUID = -8818006672883481775L; + + private Object lock; + + @SetFromFlag + protected List<MachineProvisioningLocation<T>> provisioners; + + @SetFromFlag + protected Map<T, MachineProvisioningLocation<T>> inUse; + + protected final AtomicInteger obtainCounter = new AtomicInteger(); + + public AggregatingMachineProvisioningLocation() { + this(Maps.newLinkedHashMap()); + } + + public AggregatingMachineProvisioningLocation(Map properties) { + super(properties); + + if (isLegacyConstruction()) { + init(); + } + } + + @Override + public void init() { + super.init(); + } + + @Override + public String toVerboseString() { + return Objects.toStringHelper(this).omitNullValues() + .add("id", getId()).add("name", getDisplayName()) + .add("provisioners", provisioners) + .toString(); + } + + @Override + public AbstractLocation configure(Map<?,?> properties) { + if (lock == null) { + lock = new Object(); + provisioners = Lists.<MachineProvisioningLocation<T>>newArrayList(); + inUse = Maps.<T, MachineProvisioningLocation<T>>newLinkedHashMap(); + } + return super.configure(properties); + } + + @Override + public AggregatingMachineProvisioningLocation<T> newSubLocation(Map<?,?> newFlags) { + throw new UnsupportedOperationException(); + } + + @Override + public void close() { + for (MachineProvisioningLocation<?> provisioner : provisioners) { + if (provisioner instanceof Closeable) { + Streams.closeQuietly((Closeable)provisioner); + } + } + } + + public T obtain() throws NoMachinesAvailableException { + return obtain(Maps.<String,Object>newLinkedHashMap()); + } + + @Override + public T obtain(Map<?,?> flags) throws NoMachinesAvailableException { + checkState(provisioners.size() > 0, "no provisioners!"); + int index = obtainCounter.getAndIncrement(); + for (int i = 0; i < provisioners.size(); i++) { + MachineProvisioningLocation<T> provisioner = provisioners.get(index++ % provisioners.size()); + try { + T machine = provisioner.obtain(flags); + inUse.put(machine, provisioner); + return machine; + } catch (NoMachinesAvailableException e) { + // move on; try next + } + } + throw new NoMachinesAvailableException("No machines available in "+toString()); + } + + @Override + public void release(T machine) { + MachineProvisioningLocation<T> provisioner = inUse.remove(machine); + if (provisioner != null) { + provisioner.release(machine); + } else { + throw new IllegalStateException("Request to release machine "+machine+", but this machine is not currently allocated"); + } + } + + @Override + public Map<String,Object> getProvisioningFlags(Collection<String> tags) { + return Maps.<String,Object>newLinkedHashMap(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/core/BasicHardwareDetails.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/core/BasicHardwareDetails.java b/core/src/main/java/org/apache/brooklyn/location/core/BasicHardwareDetails.java new file mode 100644 index 0000000..09059ec --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/core/BasicHardwareDetails.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.location.core; + +import javax.annotation.concurrent.Immutable; + +import com.google.common.base.Objects; + +import org.apache.brooklyn.api.location.HardwareDetails; + +@Immutable +public class BasicHardwareDetails implements HardwareDetails { + + private final Integer cpuCount; + private final Integer ram; + + public BasicHardwareDetails(Integer cpuCount, Integer ram) { + this.cpuCount = cpuCount; + this.ram = ram; + } + + @Override + public Integer getCpuCount() { + return cpuCount; + } + + @Override + public Integer getRam() { + return ram; + } + + @Override + public String toString() { + return Objects.toStringHelper(HardwareDetails.class) + .omitNullValues() + .add("cpuCount", cpuCount) + .add("ram", ram) + .toString(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/main/java/org/apache/brooklyn/location/core/BasicLocationDefinition.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/core/BasicLocationDefinition.java b/core/src/main/java/org/apache/brooklyn/location/core/BasicLocationDefinition.java new file mode 100644 index 0000000..df9c641 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/core/BasicLocationDefinition.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.location.core; + +import java.util.Map; + +import org.apache.brooklyn.api.location.LocationDefinition; +import org.apache.brooklyn.util.text.Identifiers; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; + +public class BasicLocationDefinition implements LocationDefinition { + + private final String id; + private final String name; + private final String spec; + private final Map<String,Object> config; + + public BasicLocationDefinition(String name, String spec, Map<String,? extends Object> config) { + this(Identifiers.makeRandomId(8), name, spec, config); + } + + public BasicLocationDefinition(String id, String name, String spec, Map<String,? extends Object> config) { + this.id = Preconditions.checkNotNull(id); + this.name = name; + this.spec = Preconditions.checkNotNull(spec); + this.config = config==null ? ImmutableMap.<String, Object>of() : ImmutableMap.<String, Object>copyOf(config); + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public String getSpec() { + return spec; + } + + @Override + public Map<String, Object> getConfig() { + return config; + } + + @Override + public boolean equals(Object o) { + if (this==o) return true; + if ((o instanceof LocationDefinition) && id.equals(((LocationDefinition)o).getId())) return true; + return false; + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public String toString() { + return "LocationDefinition{" + + "id='" + getId() + '\'' + + ", name='" + getName() + '\'' + + ", spec='" + getSpec() + '\'' + + ", config=" + getConfig() + + '}'; + } +}
