http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/basic/ByonLocationResolver.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/basic/ByonLocationResolver.java b/core/src/main/java/org/apache/brooklyn/location/basic/ByonLocationResolver.java new file mode 100644 index 0000000..a93d3dd --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/basic/ByonLocationResolver.java @@ -0,0 +1,251 @@ +/* + * 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.basic; + +import static com.google.common.base.Preconditions.checkArgument; + +import java.net.InetAddress; +import java.util.List; +import java.util.Map; + +import org.apache.brooklyn.location.Location; +import org.apache.brooklyn.location.LocationRegistry; +import org.apache.brooklyn.location.LocationSpec; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.net.HostAndPort; + +import brooklyn.config.ConfigKey; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.entity.basic.Sanitizer; +import org.apache.brooklyn.location.MachineLocation; +import brooklyn.management.internal.LocalLocationManager; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.config.ConfigBag; +import brooklyn.util.flags.TypeCoercions; +import brooklyn.util.net.UserAndHostAndPort; +import brooklyn.util.text.WildcardGlobs; +import brooklyn.util.text.WildcardGlobs.PhraseTreatment; + +/** + * 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({"rawtypes"}) +public class ByonLocationResolver extends AbstractLocationResolver { + + public static final Logger log = LoggerFactory.getLogger(ByonLocationResolver.class); + + public static final String BYON = "byon"; + + public static final ConfigKey<String> OS_FAMILY = ConfigKeys.newStringConfigKey("osfamily", "OS Family of the machine, either windows or linux", "linux"); + + public static final Map<String, Class<? extends MachineLocation>> OS_TO_MACHINE_LOCATION_TYPE = ImmutableMap.<String, Class<? extends MachineLocation>>of( + "windows", WinRmMachineLocation.class, + "linux", SshMachineLocation.class); + + @Override + public String getPrefix() { + return BYON; + } + + @Override + protected Class<? extends Location> getLocationType() { + return FixedListMachineProvisioningLocation.class; + } + + @Override + protected SpecParser getSpecParser() { + return new AbstractLocationResolver.SpecParser(getPrefix()).setExampleUsage("\"byon(hosts='addr1,addr2')\""); + } + + @Override + protected ConfigBag extractConfig(Map<?,?> locationFlags, String spec, LocationRegistry registry) { + ConfigBag config = super.extractConfig(locationFlags, spec, registry); + + Object hosts = config.getStringKey("hosts"); + config.remove("hosts"); + String user = (String) config.getStringKey("user"); + Integer port = (Integer) TypeCoercions.coerce(config.getStringKey("port"), Integer.class); + Class<? extends MachineLocation> locationClass = OS_TO_MACHINE_LOCATION_TYPE.get(config.get(OS_FAMILY)); + + MutableMap<String, Object> defaultProps = MutableMap.of(); + defaultProps.addIfNotNull("user", user); + defaultProps.addIfNotNull("port", port); + + List<String> hostAddresses; + + if (hosts instanceof String) { + if (((String) hosts).isEmpty()) { + hostAddresses = ImmutableList.of(); + } else { + hostAddresses = WildcardGlobs.getGlobsAfterBraceExpansion("{"+hosts+"}", + 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); + } else { + throw new IllegalArgumentException("Invalid location '"+spec+"'; at least one host must be defined"); + } + if (hostAddresses.isEmpty()) { + throw new IllegalArgumentException("Invalid location '"+spec+"'; at least one host must be defined"); + } + + List<MachineLocation> machines = Lists.newArrayList(); + for (Object host : hostAddresses) { + LocationSpec<? extends MachineLocation> machineSpec; + if (host instanceof String) { + machineSpec = parseMachine((String)host, locationClass, defaultProps, spec); + } else if (host instanceof Map) { + machineSpec = parseMachine((Map<String, ?>)host, locationClass, defaultProps, spec); + } else { + throw new IllegalArgumentException("Expected machine to be String or Map, but was "+host.getClass().getName()+" ("+host+")"); + } + machineSpec.configureIfNotNull(LocalLocationManager.CREATE_UNMANAGED, config.get(LocalLocationManager.CREATE_UNMANAGED)); + MachineLocation machine = managementContext.getLocationManager().createLocation(machineSpec); + machines.add(machine); + } + + config.putStringKey("machines", machines); + + return config; + } + + protected LocationSpec<? extends MachineLocation> parseMachine(Map<String, ?> vals, Class<? extends MachineLocation> locationClass, Map<String, ?> defaults, String specForErrMsg) { + Map<String, Object> valSanitized = Sanitizer.sanitize(vals); + Map<String, Object> machineConfig = MutableMap.copyOf(vals); + + String osfamily = (String) machineConfig.remove(OS_FAMILY.getName()); + String ssh = (String) machineConfig.remove("ssh"); + String winrm = (String) machineConfig.remove("winrm"); + Map<Integer, String> tcpPortMappings = (Map<Integer, String>) machineConfig.get("tcpPortMappings"); + + checkArgument(ssh != null ^ winrm != null, "Must specify exactly one of 'ssh' or 'winrm' for machine: %s", valSanitized); + + UserAndHostAndPort userAndHostAndPort; + String host; + int port; + if (ssh != null) { + userAndHostAndPort = parseUserAndHostAndPort((String)ssh, 22); + } else { + userAndHostAndPort = parseUserAndHostAndPort((String)winrm, 5985); + } + + // If there is a tcpPortMapping defined for the connection-port, then use that for ssh/winrm machine + port = userAndHostAndPort.getHostAndPort().getPort(); + if (tcpPortMappings != null && tcpPortMappings.containsKey(port)) { + String override = tcpPortMappings.get(port); + HostAndPort hostAndPortOverride = HostAndPort.fromString(override); + if (!hostAndPortOverride.hasPort()) { + throw new IllegalArgumentException("Invalid portMapping ('"+override+"') for port "+port+" in "+specForErrMsg); + } + port = hostAndPortOverride.getPort(); + host = hostAndPortOverride.getHostText().trim(); + } else { + host = userAndHostAndPort.getHostAndPort().getHostText().trim(); + } + + machineConfig.put("address", host); + try { + InetAddress.getByName(host); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid host '"+host+"' specified in '"+specForErrMsg+"': "+e); + } + + if (userAndHostAndPort.getUser() != null) { + checkArgument(!vals.containsKey("user"), "Must not specify user twice for machine: %s", valSanitized); + machineConfig.put("user", userAndHostAndPort.getUser()); + } + if (userAndHostAndPort.getHostAndPort().hasPort()) { + checkArgument(!vals.containsKey("port"), "Must not specify port twice for machine: %s", valSanitized); + machineConfig.put("port", port); + } + for (Map.Entry<String, ?> entry : defaults.entrySet()) { + if (!machineConfig.containsKey(entry.getKey())) { + machineConfig.put(entry.getKey(), entry.getValue()); + } + } + + Class<? extends MachineLocation> locationClassHere = locationClass; + if (osfamily != null) { + locationClassHere = OS_TO_MACHINE_LOCATION_TYPE.get(osfamily); + } + + return LocationSpec.create(locationClassHere).configure(machineConfig); + } + + protected LocationSpec<? extends MachineLocation> parseMachine(String val, Class<? extends MachineLocation> locationClass, Map<String, ?> defaults, String specForErrMsg) { + Map<String, Object> machineConfig = Maps.newLinkedHashMap(); + + UserAndHostAndPort userAndHostAndPort = parseUserAndHostAndPort(val); + + String host = userAndHostAndPort.getHostAndPort().getHostText().trim(); + machineConfig.put("address", host); + try { + InetAddress.getByName(host.trim()); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid host '"+host+"' specified in '"+specForErrMsg+"': "+e); + } + + if (userAndHostAndPort.getUser() != null) { + machineConfig.put("user", userAndHostAndPort.getUser()); + } + if (userAndHostAndPort.getHostAndPort().hasPort()) { + machineConfig.put("port", userAndHostAndPort.getHostAndPort().getPort()); + } + for (Map.Entry<String, ?> entry : defaults.entrySet()) { + if (!machineConfig.containsKey(entry.getKey())) { + machineConfig.put(entry.getKey(), entry.getValue()); + } + } + + return LocationSpec.create(locationClass).configure(machineConfig); + } + + private UserAndHostAndPort parseUserAndHostAndPort(String val) { + String userPart = null; + String hostPart = val; + if (val.contains("@")) { + userPart = val.substring(0, val.indexOf("@")); + hostPart = val.substring(val.indexOf("@")+1); + } + return UserAndHostAndPort.fromParts(userPart, HostAndPort.fromString(hostPart)); + } + + private UserAndHostAndPort parseUserAndHostAndPort(String val, int defaultPort) { + UserAndHostAndPort result = parseUserAndHostAndPort(val); + if (!result.getHostAndPort().hasPort()) { + result = UserAndHostAndPort.fromParts(result.getUser(), result.getHostAndPort().getHostText(), defaultPort); + } + return result; + } +}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/basic/CatalogLocationResolver.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/basic/CatalogLocationResolver.java b/core/src/main/java/org/apache/brooklyn/location/basic/CatalogLocationResolver.java new file mode 100644 index 0000000..7d55d57 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/basic/CatalogLocationResolver.java @@ -0,0 +1,80 @@ +/* + * 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.basic; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Map; + +import org.apache.brooklyn.location.Location; +import org.apache.brooklyn.location.LocationRegistry; +import org.apache.brooklyn.location.LocationResolver; +import org.apache.brooklyn.location.LocationSpec; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.brooklyn.api.catalog.CatalogItem; +import org.apache.brooklyn.api.management.ManagementContext; + +import brooklyn.catalog.internal.CatalogUtils; + +/** + * Given a location spec in the form {@code brooklyn.catalog:<symbolicName>:<version>}, + * looks up the catalog to get its definition and creates such a location. + */ +public class CatalogLocationResolver implements LocationResolver { + + @SuppressWarnings("unused") + private static final Logger log = LoggerFactory.getLogger(CatalogLocationResolver.class); + + public static final String NAME = "brooklyn.catalog"; + + private ManagementContext managementContext; + + @Override + public void init(ManagementContext managementContext) { + this.managementContext = checkNotNull(managementContext, "managementContext"); + } + + @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) + public Location newLocationFromString(Map locationFlags, String spec, LocationRegistry registry) { + String id = spec.substring(NAME.length()+1); + CatalogItem<?, ?> item = CatalogUtils.getCatalogItemOptionalVersion(managementContext, id); + LocationSpec<?> origLocSpec = managementContext.getCatalog().createSpec((CatalogItem<Location, LocationSpec<?>>)item); + LocationSpec<?> locSpec = LocationSpec.create(origLocSpec) + .configure(locationFlags); + return managementContext.getLocationManager().createLocation(locSpec); + } + + @Override + public String getPrefix() { + return NAME; + } + + /** + * accepts anything that looks like it will be a YAML catalog item (e.g. starting "brooklyn.locations") + */ + @Override + public boolean accepts(String spec, LocationRegistry registry) { + if (BasicLocationRegistry.isResolverPrefixForSpec(this, spec, false)) return true; + if (registry.getDefinedLocationByName(spec)!=null) return true; + return false; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/basic/DefinedLocationByIdResolver.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/basic/DefinedLocationByIdResolver.java b/core/src/main/java/org/apache/brooklyn/location/basic/DefinedLocationByIdResolver.java new file mode 100644 index 0000000..7277fc9 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/basic/DefinedLocationByIdResolver.java @@ -0,0 +1,74 @@ +/* + * 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.basic; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Map; + +import org.apache.brooklyn.api.management.ManagementContext; +import org.apache.brooklyn.location.Location; +import org.apache.brooklyn.location.LocationDefinition; +import org.apache.brooklyn.location.LocationRegistry; +import org.apache.brooklyn.location.LocationResolver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * looks up based on ID in DefinedLocations map + */ +public class DefinedLocationByIdResolver implements LocationResolver { + + public static final Logger log = LoggerFactory.getLogger(DefinedLocationByIdResolver.class); + + public static final String ID = "id"; + + private volatile ManagementContext managementContext; + + @Override + public void init(ManagementContext managementContext) { + this.managementContext = checkNotNull(managementContext, "managementContext"); + } + + @SuppressWarnings({ "rawtypes" }) + @Override + public Location newLocationFromString(Map locationFlags, String spec, LocationRegistry registry) { + String id = spec; + if (spec.toLowerCase().startsWith(ID+":")) { + id = spec.substring( (ID+":").length() ); + } + LocationDefinition ld = registry.getDefinedLocationById(id); + ld.getSpec(); + return ((BasicLocationRegistry)registry).resolveLocationDefinition(ld, locationFlags, null); + } + + @Override + public String getPrefix() { + return ID; + } + + /** accepts anything starting id:xxx or just xxx where xxx is a defined location ID */ + @Override + public boolean accepts(String spec, LocationRegistry registry) { + if (BasicLocationRegistry.isResolverPrefixForSpec(this, spec, false)) return true; + if (registry.getDefinedLocationById(spec)!=null) return true; + return false; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/basic/DeprecatedKeysMappingBuilder.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/basic/DeprecatedKeysMappingBuilder.java b/core/src/main/java/org/apache/brooklyn/location/basic/DeprecatedKeysMappingBuilder.java new file mode 100644 index 0000000..88593d4 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/basic/DeprecatedKeysMappingBuilder.java @@ -0,0 +1,67 @@ +/* + * 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.basic; + +import java.util.Map; + +import org.slf4j.Logger; + +import brooklyn.config.ConfigKey; + +import com.google.common.base.CaseFormat; +import com.google.common.collect.ImmutableMap; + +/** +* @deprecated since 0.6; for use only in converting deprecated flags; will be deleted in future version. +*/ +public class DeprecatedKeysMappingBuilder { + private final ImmutableMap.Builder<String,String> builder = new ImmutableMap.Builder<String,String>(); + private final Logger logger; + + public DeprecatedKeysMappingBuilder(Logger logger) { + this.logger = logger; + } + + public DeprecatedKeysMappingBuilder camelToHyphen(ConfigKey<?> key) { + return camelToHyphen(key.getName()); + } + + public DeprecatedKeysMappingBuilder camelToHyphen(String key) { + String hyphen = toHyphen(key); + if (key.equals(hyphen)) { + logger.warn("Invalid attempt to convert camel-case key {} to deprecated hyphen-case: both the same", hyphen); + } else { + builder.put(hyphen, key); + } + return this; + } + + public DeprecatedKeysMappingBuilder putAll(Map<String,String> vals) { + builder.putAll(vals); + return this; + } + + public Map<String,String> build() { + return builder.build(); + } + + private String toHyphen(String word) { + return CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, word); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/basic/FixedListMachineProvisioningLocation.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/basic/FixedListMachineProvisioningLocation.java b/core/src/main/java/org/apache/brooklyn/location/basic/FixedListMachineProvisioningLocation.java new file mode 100644 index 0000000..45bc585 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/basic/FixedListMachineProvisioningLocation.java @@ -0,0 +1,474 @@ +/* + * 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.basic; + +import static 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.management.LocationManager; +import org.apache.brooklyn.location.Location; +import org.apache.brooklyn.location.LocationSpec; +import org.apache.brooklyn.location.MachineLocationCustomizer; +import org.apache.brooklyn.location.NoMachinesAvailableException; +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 brooklyn.config.ConfigKey; +import brooklyn.entity.basic.ConfigKeys; +import org.apache.brooklyn.location.MachineLocation; +import org.apache.brooklyn.location.MachineProvisioningLocation; +import org.apache.brooklyn.location.cloud.CloudLocationConfig; +import brooklyn.util.collections.CollectionFunctionals; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.collections.MutableSet; +import brooklyn.util.config.ConfigBag; +import brooklyn.util.flags.SetFromFlag; +import brooklyn.util.stream.Streams; +import brooklyn.util.text.WildcardGlobs; +import 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/e2c57058/core/src/main/java/org/apache/brooklyn/location/basic/HasSubnetHostname.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/basic/HasSubnetHostname.java b/core/src/main/java/org/apache/brooklyn/location/basic/HasSubnetHostname.java new file mode 100644 index 0000000..e9fbece --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/basic/HasSubnetHostname.java @@ -0,0 +1,32 @@ +/* + * 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.basic; + +import com.google.common.annotations.Beta; + +@Beta +public interface HasSubnetHostname { + + /** returns a hostname for use internally within a subnet / VPC */ + @Beta + String getSubnetHostname(); + + /** returns an IP for use internally within a subnet / VPC */ + String getSubnetIp(); +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/basic/HostLocationResolver.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/basic/HostLocationResolver.java b/core/src/main/java/org/apache/brooklyn/location/basic/HostLocationResolver.java new file mode 100644 index 0000000..0e5225b --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/basic/HostLocationResolver.java @@ -0,0 +1,90 @@ +/* + * 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.basic; + +import java.util.Map; + +import org.apache.brooklyn.location.Location; +import org.apache.brooklyn.location.LocationRegistry; +import org.apache.brooklyn.location.LocationSpec; +import org.apache.brooklyn.location.basic.AbstractLocationResolver.SpecParser.ParsedSpec; +import brooklyn.util.config.ConfigBag; +import brooklyn.util.exceptions.Exceptions; +import brooklyn.util.guava.Maybe; +import brooklyn.util.guava.Maybe.Absent; +import 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/e2c57058/core/src/main/java/org/apache/brooklyn/location/basic/LocalhostLocationResolver.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/basic/LocalhostLocationResolver.java b/core/src/main/java/org/apache/brooklyn/location/basic/LocalhostLocationResolver.java new file mode 100644 index 0000000..47c8089 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/basic/LocalhostLocationResolver.java @@ -0,0 +1,74 @@ +/* + * 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.basic; + +import java.util.Map; + +import org.apache.brooklyn.location.Location; +import brooklyn.util.config.ConfigBag; +import org.apache.brooklyn.location.LocationRegistry; +import org.apache.brooklyn.location.LocationResolver; + +/** + * Examples of valid specs: + * <ul> + * <li>localhost + * <li>localhost() + * <li>localhost(name=abc) + * <li>localhost(name="abc") + * </ul> + * + * @author alex, aled + */ +public class LocalhostLocationResolver extends AbstractLocationResolver implements LocationResolver.EnableableLocationResolver { + + public static final String LOCALHOST = "localhost"; + + @Override + public String getPrefix() { + return LOCALHOST; + } + + @Override + public boolean isEnabled() { + return LocationConfigUtils.isEnabled(managementContext, "brooklyn.location.localhost"); + } + + @Override + protected Class<? extends Location> getLocationType() { + return LocalhostMachineProvisioningLocation.class; + } + + @Override + protected SpecParser getSpecParser() { + return new AbstractLocationResolver.SpecParser(getPrefix()).setExampleUsage("\"localhost\" or \"localhost(displayName=abc)\""); + } + + @Override + protected Map<String, Object> getFilteredLocationProperties(String provider, String namedLocation, Map<String, ?> globalProperties) { + return new LocalhostPropertiesFromBrooklynProperties().getLocationProperties("localhost", namedLocation, globalProperties); + } + + @Override + protected ConfigBag extractConfig(Map<?,?> locationFlags, String spec, LocationRegistry registry) { + ConfigBag config = super.extractConfig(locationFlags, spec, registry); + config.putAsStringKeyIfAbsent("name", "localhost"); + return config; + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/basic/LocalhostMachineProvisioningLocation.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/basic/LocalhostMachineProvisioningLocation.java b/core/src/main/java/org/apache/brooklyn/location/basic/LocalhostMachineProvisioningLocation.java new file mode 100644 index 0000000..44b95b6 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/basic/LocalhostMachineProvisioningLocation.java @@ -0,0 +1,347 @@ +/* + * 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.basic; + +import static brooklyn.util.GroovyJavaMethods.elvis; +import static brooklyn.util.GroovyJavaMethods.truth; + +import java.io.File; +import java.net.InetAddress; +import java.util.Arrays; +import java.util.Map; +import java.util.Set; + +import org.apache.brooklyn.location.AddressableLocation; +import org.apache.brooklyn.location.LocationSpec; +import org.apache.brooklyn.location.MachineProvisioningLocation; +import org.apache.brooklyn.location.OsDetails; +import org.apache.brooklyn.location.PortRange; +import org.apache.brooklyn.location.geo.HostGeoInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.config.ConfigKey; +import brooklyn.config.ConfigKey.HasConfigKey; +import brooklyn.entity.basic.BrooklynConfigKeys; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.entity.rebind.persister.FileBasedObjectStore; +import brooklyn.entity.rebind.persister.LocationWithObjectStore; +import brooklyn.entity.rebind.persister.PersistenceObjectStore; +import brooklyn.util.BrooklynNetworkUtils; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.flags.SetFromFlag; +import brooklyn.util.internal.ssh.process.ProcessTool; +import brooklyn.util.mutex.MutexSupport; +import brooklyn.util.mutex.WithMutexes; +import brooklyn.util.net.Networking; +import brooklyn.util.os.Os; +import brooklyn.util.ssh.BashCommands; +import brooklyn.util.time.Duration; +import brooklyn.util.time.Time; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +/** + * An implementation of {@link MachineProvisioningLocation} that can provision a {@link SshMachineLocation} for the + * local host. + * + * By default you can only obtain a single SshMachineLocation for the localhost. Optionally, you can "overload" + * and choose to allow localhost to be provisioned multiple times, which may be useful in some testing scenarios. + */ +public class LocalhostMachineProvisioningLocation extends FixedListMachineProvisioningLocation<SshMachineLocation> implements AddressableLocation, LocationWithObjectStore { + + public static final Logger LOG = LoggerFactory.getLogger(LocalhostMachineProvisioningLocation.class); + + public static final ConfigKey<Boolean> SKIP_ON_BOX_BASE_DIR_RESOLUTION = ConfigKeys.newConfigKeyWithDefault( + BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION, + true); + + @SetFromFlag("count") + int initialCount; + + @SetFromFlag + Boolean canProvisionMore; + + @SetFromFlag + InetAddress address; + + private static Set<Integer> portsInUse = Sets.newLinkedHashSet(); + + private static HostGeoInfo cachedHostGeoInfo; + + @VisibleForTesting + public static void clearStaticData() { + portsInUse.clear(); + cachedHostGeoInfo = null; + } + + /** + * Construct a new instance. + * + * The constructor recognises the following properties: + * <ul> + * <li>count - number of localhost machines to make available + * </ul> + */ + public LocalhostMachineProvisioningLocation() { + this(Maps.newLinkedHashMap()); + } + /** + * @param properties the properties of the new instance. + * @deprecated since 0.6 + * @see #LocalhostMachineProvisioningLocation() + */ + public LocalhostMachineProvisioningLocation(Map properties) { + super(properties); + } + public LocalhostMachineProvisioningLocation(String name) { + this(name, 0); + } + public LocalhostMachineProvisioningLocation(String name, int count) { + this(MutableMap.of("name", name, "count", count)); + } + + public static LocationSpec<LocalhostMachineProvisioningLocation> spec() { + return LocationSpec.create(LocalhostMachineProvisioningLocation.class); + } + + public LocalhostMachineProvisioningLocation configure(Map<?,?> flags) { + super.configure(flags); + + if (!truth(getDisplayName())) { setDisplayName("localhost"); } + if (!truth(address)) address = getLocalhostInetAddress(); + // TODO should try to confirm this machine is accessible on the given address ... but there's no + // immediate convenience in java so early-trapping of that particular error is deferred + + if (canProvisionMore==null) { + if (initialCount>0) canProvisionMore = false; + else canProvisionMore = true; + } + if (getHostGeoInfo()==null) { + if (cachedHostGeoInfo==null) + cachedHostGeoInfo = HostGeoInfo.fromLocation(this); + setHostGeoInfo(cachedHostGeoInfo); + } + if (initialCount > getMachines().size()) { + provisionMore(initialCount - getMachines().size()); + } + + if (getConfig(BrooklynConfigKeys.ONBOX_BASE_DIR)==null && (getManagementContext()==null || getManagementContext().getConfig().getConfig(BrooklynConfigKeys.ONBOX_BASE_DIR)==null)) { + setConfig(BrooklynConfigKeys.ONBOX_BASE_DIR, "/tmp/brooklyn-"+Os.user()); + } + + return this; + } + + public static InetAddress getLocalhostInetAddress() { + return BrooklynNetworkUtils.getLocalhostInetAddress(); + } + + @Override + public InetAddress getAddress() { + return address; + } + + @Override + public boolean canProvisionMore() { + return canProvisionMore; + } + + @Override + protected void provisionMore(int size, Map<?,?> flags) { + for (int i=0; i<size; i++) { + Map<Object,Object> flags2 = MutableMap.<Object,Object>builder() + .putAll(flags) + .put("address", elvis(address, Networking.getLocalHost())) + .put("mutexSupport", LocalhostMachine.mutexSupport) + .build(); + + // copy inherited keys for ssh; + // shouldn't be necessary but not sure that all contexts traverse the hierarchy + // NOTE: changed Nov 2013 to copy only those ssh config keys actually set, rather than all of them + // TODO should take the plunge and try removing this altogether! + // (or alternatively switch to copying all ancestor keys) + for (HasConfigKey<?> k: SshMachineLocation.ALL_SSH_CONFIG_KEYS) { + if (config().getRaw(k).isPresent()) + flags2.put(k, getConfig(k)); + } + + if (isManaged()) { + addChild(LocationSpec.create(LocalhostMachine.class).configure(flags2)); + } else { + addChild(new LocalhostMachine(flags2)); // TODO legacy way + } + } + } + + public static synchronized boolean obtainSpecificPort(InetAddress localAddress, int portNumber) { + if (portsInUse.contains(portNumber)) { + return false; + } else { + //see if it is available? + if (!checkPortAvailable(localAddress, portNumber)) { + return false; + } + portsInUse.add(portNumber); + return true; + } + } + /** checks the actual availability of the port on localhost, ie by binding to it; cf {@link Networking#isPortAvailable(int)} */ + public static boolean checkPortAvailable(InetAddress localAddress, int portNumber) { + if (portNumber<1024) { + if (LOG.isDebugEnabled()) LOG.debug("Skipping system availability check for privileged localhost port "+portNumber); + return true; + } + return Networking.isPortAvailable(localAddress, portNumber); + } + public static int obtainPort(PortRange range) { + return obtainPort(getLocalhostInetAddress(), range); + } + public static int obtainPort(InetAddress localAddress, PortRange range) { + for (int p: range) + if (obtainSpecificPort(localAddress, p)) return p; + if (LOG.isDebugEnabled()) LOG.debug("unable to find port in {} on {}; returning -1", range, localAddress); + return -1; + } + + public static synchronized void releasePort(InetAddress localAddress, int portNumber) { + portsInUse.remove((Object) portNumber); + } + + public void release(SshMachineLocation machine) { + LocalhostMachine localMachine = (LocalhostMachine) machine; + Set<Integer> portsObtained = Sets.newLinkedHashSet(); + synchronized (localMachine.portsObtained) { + portsObtained.addAll(localMachine.portsObtained); + } + + super.release(machine); + + for (int p: portsObtained) + releasePort(null, p); + } + + public static class LocalhostMachine extends SshMachineLocation implements HasSubnetHostname { + // declaring this here (as well as on LocalhostMachineProvisioningLocation) because: + // 1. machine.getConfig(key) will not inherit default value of machine.getParent()'s key + // 2. things might instantiate a `LocalhostMachine` without going through LocalhostMachineProvisioningLocation + // so not sufficient for LocalhostMachineProvisioningLocation to just push its config value into + // the LocalhostMachine instance. + public static final ConfigKey<Boolean> SKIP_ON_BOX_BASE_DIR_RESOLUTION = ConfigKeys.newConfigKeyWithDefault( + BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION, + true); + + private static final WithMutexes mutexSupport = new MutexSupport(); + + private final Set<Integer> portsObtained = Sets.newLinkedHashSet(); + + public LocalhostMachine() { + super(); + } + /** @deprecated since 0.6.0 use no-arg constructor (and spec) then configure */ + public LocalhostMachine(Map properties) { + super(MutableMap.builder().putAll(properties).put("mutexSupport", mutexSupport).build()); + } + + @Override + protected WithMutexes getMutexSupport() { + return mutexSupport; + } + + public boolean obtainSpecificPort(int portNumber) { + if (!isSudoAllowed() && portNumber <= 1024) + return false; + return LocalhostMachineProvisioningLocation.obtainSpecificPort(getAddress(), portNumber); + } + + public int obtainPort(PortRange range) { + int r = LocalhostMachineProvisioningLocation.obtainPort(getAddress(), range); + synchronized (portsObtained) { + if (r>0) portsObtained.add(r); + } + LOG.debug("localhost.obtainPort("+range+"), returning "+r); + return r; + } + + @Override + public void releasePort(int portNumber) { + synchronized (portsObtained) { + portsObtained.remove((Object)portNumber); + } + LocalhostMachineProvisioningLocation.releasePort(getAddress(), portNumber); + } + + @Override + public OsDetails getOsDetails() { + return BasicOsDetails.Factory.newLocalhostInstance(); + } + + @Override + public LocalhostMachine configure(Map<?,?> properties) { + if (address==null || !properties.containsKey("address")) + address = Networking.getLocalHost(); + super.configure(properties); + return this; + } + @Override + public String getSubnetHostname() { + return Networking.getLocalHost().getHostName(); + } + @Override + public String getSubnetIp() { + return Networking.getLocalHost().getHostAddress(); + } + } + + private static class SudoChecker { + static volatile long lastSudoCheckTime = -1; + static boolean lastSudoResult = false; + public static boolean isSudoAllowed() { + if (Time.hasElapsedSince(lastSudoCheckTime, Duration.FIVE_MINUTES)) + checkIfNeeded(); + return lastSudoResult; + } + private static synchronized void checkIfNeeded() { + if (Time.hasElapsedSince(lastSudoCheckTime, Duration.FIVE_MINUTES)) { + try { + lastSudoResult = new ProcessTool().execCommands(MutableMap.<String,Object>of(), Arrays.asList( + BashCommands.sudo("date"))) == 0; + } catch (Exception e) { + lastSudoResult = false; + LOG.debug("Error checking sudo at localhost: "+e, e); + } + lastSudoCheckTime = System.currentTimeMillis(); + } + } + } + + public static boolean isSudoAllowed() { + return SudoChecker.isSudoAllowed(); + } + + @Override + public PersistenceObjectStore newPersistenceObjectStore(String container) { + File basedir = new File(container); + if (basedir.isFile()) throw new IllegalArgumentException("Destination directory must not be a file"); + return new FileBasedObjectStore(basedir); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/basic/LocalhostPropertiesFromBrooklynProperties.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/basic/LocalhostPropertiesFromBrooklynProperties.java b/core/src/main/java/org/apache/brooklyn/location/basic/LocalhostPropertiesFromBrooklynProperties.java new file mode 100644 index 0000000..1830ddd --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/basic/LocalhostPropertiesFromBrooklynProperties.java @@ -0,0 +1,57 @@ +/* + * 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.basic; + +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.util.config.ConfigBag; + +import com.google.common.base.Strings; + +/** + * @author aledsage + **/ +public class LocalhostPropertiesFromBrooklynProperties extends LocationPropertiesFromBrooklynProperties { + + // TODO Once delete support for deprecated "location.localhost.*" then can get rid of this class, and use + // LocationPropertiesFromBrooklynProperties directly + + @SuppressWarnings("unused") + private static final Logger LOG = LoggerFactory.getLogger(LocalhostPropertiesFromBrooklynProperties.class); + + @Override + public Map<String, Object> getLocationProperties(String provider, String namedLocation, Map<String, ?> properties) { + if (Strings.isNullOrEmpty(namedLocation) && Strings.isNullOrEmpty(provider)) { + throw new IllegalArgumentException("Neither cloud provider/API nor location name have been specified correctly"); + } + + ConfigBag result = ConfigBag.newInstance(); + + result.putAll(transformDeprecated(getGenericLocationSingleWordProperties(properties))); + result.putAll(transformDeprecated(getMatchingSingleWordProperties("brooklyn.location.", properties))); + result.putAll(transformDeprecated(getMatchingProperties("brooklyn.location.localhost.", "brooklyn.localhost.", properties))); + if (!Strings.isNullOrEmpty(namedLocation)) result.putAll(transformDeprecated(getNamedLocationProperties(namedLocation, properties))); + setLocalTempDir(properties, result); + + return result.getAllConfigRaw(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/basic/LocationConfigKeys.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/basic/LocationConfigKeys.java b/core/src/main/java/org/apache/brooklyn/location/basic/LocationConfigKeys.java new file mode 100644 index 0000000..05e15f9 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/basic/LocationConfigKeys.java @@ -0,0 +1,79 @@ +/* + * 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.basic; + +import java.io.File; +import java.util.Set; + +import brooklyn.config.ConfigKey; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.event.basic.BasicConfigKey; +import brooklyn.util.os.Os; + +import com.google.common.base.CaseFormat; +import com.google.common.reflect.TypeToken; + +public class LocationConfigKeys { + + public static final ConfigKey<String> LOCATION_ID = ConfigKeys.newStringConfigKey("id"); + public static final ConfigKey<String> DISPLAY_NAME = ConfigKeys.newStringConfigKey("displayName"); + public static final ConfigKey<Boolean> ENABLED = ConfigKeys.newBooleanConfigKey("enabled", "Whether the location is enabled for listing and use " + + "(only supported for selected locations)", true); + + public static final ConfigKey<String> ACCESS_IDENTITY = ConfigKeys.newStringConfigKey("identity"); + public static final ConfigKey<String> ACCESS_CREDENTIAL = ConfigKeys.newStringConfigKey("credential"); + + public static final ConfigKey<Double> LATITUDE = new BasicConfigKey<Double>(Double.class, "latitude"); + public static final ConfigKey<Double> LONGITUDE = new BasicConfigKey<Double>(Double.class, "longitude"); + + public static final ConfigKey<String> CLOUD_PROVIDER = ConfigKeys.newStringConfigKey("provider"); + public static final ConfigKey<String> CLOUD_ENDPOINT = ConfigKeys.newStringConfigKey("endpoint"); + public static final ConfigKey<String> CLOUD_REGION_ID = ConfigKeys.newStringConfigKey("region"); + public static final ConfigKey<String> CLOUD_AVAILABILITY_ZONE_ID = ConfigKeys.newStringConfigKey("availabilityZone"); + + @SuppressWarnings("serial") + public static final ConfigKey<Set<String>> ISO_3166 = ConfigKeys.newConfigKey(new TypeToken<Set<String>>() {}, "iso3166", "ISO-3166 or ISO-3166-2 location codes"); + + public static final ConfigKey<String> USER = ConfigKeys.newStringConfigKey("user", + "user account for normal access to the remote machine, defaulting to local user", System.getProperty("user.name")); + + public static final ConfigKey<String> PASSWORD = ConfigKeys.newStringConfigKey("password", "password to use for ssh; note some images do not allow password-based ssh access"); + public static final ConfigKey<String> PUBLIC_KEY_FILE = ConfigKeys.newStringConfigKey("publicKeyFile", "ssh public key file to use; if blank will infer from privateKeyFile by appending \".pub\""); + public static final ConfigKey<String> PUBLIC_KEY_DATA = ConfigKeys.newStringConfigKey("publicKeyData", "ssh public key string to use (takes precedence over publicKeyFile)"); + public static final ConfigKey<String> PRIVATE_KEY_FILE = ConfigKeys.newStringConfigKey("privateKeyFile", "a '" + File.pathSeparator + "' separated list of ssh private key files; uses first in list that can be read", + Os.fromHome(".ssh/id_rsa") + File.pathSeparator + Os.fromHome(".ssh/id_dsa")); + public static final ConfigKey<String> PRIVATE_KEY_DATA = ConfigKeys.newStringConfigKey("privateKeyData", "ssh private key string to use (takes precedence over privateKeyFile)"); + public static final ConfigKey<String> PRIVATE_KEY_PASSPHRASE = ConfigKeys.newStringConfigKey("privateKeyPassphrase"); + + /** @deprecated since 0.6.0; included here so it gets picked up in auto-detect routines */ @Deprecated + public static final ConfigKey<String> LEGACY_PUBLIC_KEY_FILE = ConfigKeys.convert(PUBLIC_KEY_FILE, CaseFormat.LOWER_CAMEL, CaseFormat.LOWER_HYPHEN); + /** @deprecated since 0.6.0; included here so it gets picked up in auto-detect routines */ @Deprecated + public static final ConfigKey<String> LEGACY_PUBLIC_KEY_DATA = ConfigKeys.convert(PUBLIC_KEY_DATA, CaseFormat.LOWER_CAMEL, CaseFormat.LOWER_HYPHEN); + /** @deprecated since 0.6.0; included here so it gets picked up in auto-detect routines */ @Deprecated + public static final ConfigKey<String> LEGACY_PRIVATE_KEY_FILE = ConfigKeys.convert(PRIVATE_KEY_FILE, CaseFormat.LOWER_CAMEL, CaseFormat.LOWER_HYPHEN); + /** @deprecated since 0.6.0; included here so it gets picked up in auto-detect routines */ @Deprecated + public static final ConfigKey<String> LEGACY_PRIVATE_KEY_DATA = ConfigKeys.convert(PRIVATE_KEY_DATA, CaseFormat.LOWER_CAMEL, CaseFormat.LOWER_HYPHEN); + /** @deprecated since 0.6.0; included here so it gets picked up in auto-detect routines */ @Deprecated + public static final ConfigKey<String> LEGACY_PRIVATE_KEY_PASSPHRASE = ConfigKeys.convert(PRIVATE_KEY_PASSPHRASE, CaseFormat.LOWER_CAMEL, CaseFormat.LOWER_HYPHEN); + + public static final ConfigKey<Object> CALLER_CONTEXT = new BasicConfigKey<Object>(Object.class, "callerContext", + "An object whose toString is used for logging, to indicate wherefore a VM is being created"); + public static final ConfigKey<String> CLOUD_MACHINE_NAMER_CLASS = ConfigKeys.newStringConfigKey("cloudMachineNamer", "fully qualified class name of a class that extends CloudMachineNamer and has a single-parameter constructor that takes a ConfigBag"); + +}
