Add Vagrant provider
Project: http://git-wip-us.apache.org/repos/asf/jclouds-labs/repo Commit: http://git-wip-us.apache.org/repos/asf/jclouds-labs/commit/fadcd5d9 Tree: http://git-wip-us.apache.org/repos/asf/jclouds-labs/tree/fadcd5d9 Diff: http://git-wip-us.apache.org/repos/asf/jclouds-labs/diff/fadcd5d9 Branch: refs/heads/2.0.x Commit: fadcd5d9f715f6661d550f613a83b0ab50dc718b Parents: 5343918 Author: Svetoslav Neykov <[email protected]> Authored: Thu Apr 2 22:35:47 2015 +0300 Committer: Ignasi Barrera <[email protected]> Committed: Wed Jan 25 10:58:40 2017 +0100 ---------------------------------------------------------------------- pom.xml | 2 + vagrant/README.md | 122 ++++++ vagrant/pom.xml | 129 ++++++ .../org/jclouds/vagrant/VagrantApiMetadata.java | 81 ++++ .../jclouds/vagrant/api/VagrantApiFacade.java | 42 ++ .../compute/VagrantComputeServiceAdapter.java | 401 +++++++++++++++++++ .../config/PersistVagrantCredentialsModule.java | 148 +++++++ .../VagrantComputeServiceContextModule.java | 109 +++++ .../org/jclouds/vagrant/domain/VagrantNode.java | 70 ++++ .../jclouds/vagrant/functions/BoxToImage.java | 75 ++++ .../functions/MachineToNodeMetadata.java | 120 ++++++ .../vagrant/functions/OutdatedBoxesFilter.java | 69 ++++ .../org/jclouds/vagrant/internal/BoxConfig.java | 110 +++++ .../jclouds/vagrant/internal/MachineConfig.java | 116 ++++++ .../vagrant/internal/VagrantCliFacade.java | 109 +++++ .../vagrant/internal/VagrantNodeRegistry.java | 112 ++++++ .../vagrant/internal/VagrantOutputRecorder.java | 59 +++ .../vagrant/internal/VagrantWireLogger.java | 65 +++ .../vagrant/reference/VagrantConstants.java | 66 +++ .../VagrantDefaultImageCredentials.java | 120 ++++++ .../suppliers/VagrantHardwareSupplier.java | 54 +++ .../org/jclouds/vagrant/util/VagrantUtils.java | 67 ++++ vagrant/src/main/resources/Vagrantfile | 80 ++++ .../vagrant/compute/BoxConfigLiveTest.java | 60 +++ .../VagrantComputeServiceAdapterLiveTest.java | 85 ++++ .../compute/VagrantTemplateBuilderLiveTest.java | 60 +++ .../vagrant/compute/WindowsLiveTest.java | 106 +++++ .../vagrant/functions/BoxToImageTest.java | 74 ++++ .../functions/MachineToNodeMetadataTest.java | 295 ++++++++++++++ .../functions/OutdatedBoxesFilterTest.java | 90 +++++ .../jclouds/vagrant/internal/BoxConfigTest.java | 66 +++ .../vagrant/internal/MachineConfigTest.java | 97 +++++ .../internal/VagrantNodeRegistryTest.java | 80 ++++ .../internal/VagrantOutputRecorderTest.java | 58 +++ .../vagrant/internal/VagrantWireLoggerTest.java | 54 +++ .../VagrantDefaultImageCredentialsTest.java | 179 +++++++++ .../src/test/resources/Vagrantfile.boxconfig | 25 ++ vagrant/src/test/resources/logback-test.xml | 37 ++ vagrant/src/test/resources/machine-config.yaml | 20 + 39 files changed, 3712 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index 7df21cb..b010cd2 100644 --- a/pom.xml +++ b/pom.xml @@ -82,6 +82,8 @@ <module>abiquo</module> <module>profitbricks-rest</module> <module>oneandone</module> + <module>packet</module> + <module>vagrant</module> </modules> <build> http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/README.md ---------------------------------------------------------------------- diff --git a/vagrant/README.md b/vagrant/README.md new file mode 100644 index 0000000..3fa71a3 --- /dev/null +++ b/vagrant/README.md @@ -0,0 +1,122 @@ +Vagrant provider for jclouds +============================ + +Building +-------- + + * `git clone https://github.com/jclouds/jclouds-labs` + * `cd jclouds-labs/vagrant` + * `mvn clean install` + * Copy `target/vagrant-2.0.0-SNAPSHOT.jar` to your classpath + +Local caching proxy +------------------- + +### Polipo + +Use `polipo` for caching proxy. On OS X install with + +``` +brew install polipo +``` + +From [SO](http://superuser.com/questions/192696/how-can-i-make-tor-and-polipo-run-and-automatically-restart-using-launchd-on-m): + +* Create a config file at ~/.polipo/config + +``` +# logLevel = 0xFF +dnsNameServer=8.8.8.8 +diskCacheRoot = "~/.polipo/cache/" + +``` + +* As root create the file `/Library/LaunchDaemons/fr.jussieu.pps.polipo.plist`, replace $USER with your username: +``` +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>Disabled</key> + <false/> + <key>Label</key> + <string>fr.jussieu.pps.polipo</string> + <key>ProgramArguments</key> + <array> + <string>/usr/local/bin/polipo</string> + <string>-c</string> + <string>/Users/$USER/.polipo/config</string> + </array> + <key>RunAtLoad</key> + <true/> + <key>OnDemand</key> + <false/> + <key>UserName</key> + <string>$USER</string> + <key>GroupName</key> + <string>daemon</string> + <key>StandardOutPath</key> + <string>/Users/$USER/.polipo/polipo.log</string> + <key>StandardErrorPath</key> + <string>/Users/$USER/.polipo/polipo.log</string> +</dict> +</plist> +``` + +* `sudo chown root:wheel /Library/LaunchDaemons/fr.jussieu.pps.polipo.plist` +* `sudo chmod 755 /Library/LaunchDaemons/fr.jussieu.pps.polipo.plist` +* `sudo launchctl load -w /Library/LaunchDaemons/fr.jussieu.pps.polipo.plist` + +### Vagrant + +* `vagrant plugin install vagrant-proxyconf` +* add to `~/.vagrant.d/Vagrantfile`: + +``` +Vagrant.configure("2") do |config| + if Vagrant.has_plugin?("vagrant-proxyconf") + config.proxy.http = "http://10.0.2.2:8123/" + config.proxy.https = "http://10.0.2.2:8123/" + config.proxy.no_proxy = "localhost,127.0.0.1" + end +end +``` + +Where `10.0.2.2` is the IP of your host as seen from the vagrant machines (in this case the NAT interface). +Optionally could add all your private network IPs from your Vagrant subnet to `no_proxy` to skip the proxy for inter-VM communications. + +Testing +----------- + +``` +mvn clean install -Plive +``` + +Cleaning up +----------- + +Sometimes users (or tests) do not stop correctly the machines so they need to be destroyed manually periodically. +All machines live in `~/.jclouds/vagrant`. Create `cleanup.sh` in the folder and execute it to destroy machines created by the provider: + +``` +for node in `find ~/.jclouds/vagrant -name Vagrantfile | xargs -n1 dirname`; do + pushd $node > /dev/null + echo Destroying $node + vagrant destroy --force + popd> /dev/null + rm -rf $machine +done +``` + +Same as a one-liner + +``` +for f in `find ~/.jclouds/vagrant/tests -name Vagrantfile | xargs -n1 dirname`; do pushd $f; vagrant destroy --force; popd; rm -rf $f; done +``` + + +Limitations +----------- + +* Machines are created sequentially, no support for parallel execution from virtualbox provider +* Something prevents using vagrant at the same time with other jclouds providers - they try to login with vagrant user. http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/pom.xml ---------------------------------------------------------------------- diff --git a/vagrant/pom.xml b/vagrant/pom.xml new file mode 100644 index 0000000..a790f1b --- /dev/null +++ b/vagrant/pom.xml @@ -0,0 +1,129 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + 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. + +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.jclouds.labs</groupId> + <artifactId>jclouds-labs</artifactId> + <version>2.0.1-SNAPSHOT</version> + </parent> + + <artifactId>vagrant</artifactId> + <name>Vagrant provider</name> + <packaging>bundle</packaging> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <test.vagrant.template>imageId=ubuntu/xenial64</test.vagrant.template> + <jclouds.osgi.export>org.jclouds.vagrant*;version="${project.version}"</jclouds.osgi.export> + <jclouds.osgi.import>org.jclouds*;version="${project.version}",*</jclouds.osgi.import> + </properties> + + <dependencies> + <dependency> + <groupId>name.neykov</groupId> + <artifactId>vagrant-java-bindings</artifactId> + <version>0.1.0</version> + </dependency> + <dependency> + <groupId>org.apache.jclouds</groupId> + <artifactId>jclouds-compute</artifactId> + <version>${project.parent.version}</version> + </dependency> + <dependency> + <groupId>com.google.auto.service</groupId> + <artifactId>auto-service</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.google.auto.value</groupId> + <artifactId>auto-value</artifactId> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>org.apache.jclouds</groupId> + <artifactId>jclouds-core</artifactId> + <version>${project.parent.version}</version> + <type>test-jar</type> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.jclouds</groupId> + <artifactId>jclouds-compute</artifactId> + <version>${project.parent.version}</version> + <type>test-jar</type> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.jclouds.driver</groupId> + <artifactId>jclouds-slf4j</artifactId> + <version>${project.parent.version}</version> + <type>test-jar</type> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.jclouds.driver</groupId> + <artifactId>jclouds-slf4j</artifactId> + <version>${project.parent.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-classic</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.jclouds.driver</groupId> + <artifactId>jclouds-sshj</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + </dependencies> + + <profiles> + <profile> + <id>live</id> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <executions> + <execution> + <id>integration</id> + <phase>integration-test</phase> + <goals> + <goal>test</goal> + </goals> + <configuration> + <systemPropertyVariables> + <test.vagrant.template>${test.vagrant.template}</test.vagrant.template> + </systemPropertyVariables> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + </profiles> +</project> http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/VagrantApiMetadata.java ---------------------------------------------------------------------- diff --git a/vagrant/src/main/java/org/jclouds/vagrant/VagrantApiMetadata.java b/vagrant/src/main/java/org/jclouds/vagrant/VagrantApiMetadata.java new file mode 100644 index 0000000..deebb81 --- /dev/null +++ b/vagrant/src/main/java/org/jclouds/vagrant/VagrantApiMetadata.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.jclouds.vagrant; + +import java.net.URI; +import java.util.Properties; + +import org.jclouds.apis.ApiMetadata; +import org.jclouds.apis.internal.BaseApiMetadata; +import org.jclouds.compute.ComputeServiceContext; +import org.jclouds.compute.config.ComputeServiceProperties; +import org.jclouds.vagrant.config.VagrantComputeServiceContextModule; +import org.jclouds.vagrant.reference.VagrantConstants; + +import com.google.auto.service.AutoService; + +@AutoService(ApiMetadata.class) +public class VagrantApiMetadata extends BaseApiMetadata { + + public VagrantApiMetadata() { + this(new Builder()); + } + + protected VagrantApiMetadata(Builder builder) { + super(builder); + } + + @Override + public Builder toBuilder() { + return new Builder().fromApiMetadata(this); + } + + public static class Builder extends BaseApiMetadata.Builder<Builder> { + + protected Builder() { + id("vagrant") + .name("Vagrant API") + .identityName("User") + .credentialName("Password") + .defaultEndpoint("https://atlas.hashicorp.com/") + .documentation(URI.create("https://www.vagrantup.com/docs")) + .view(ComputeServiceContext.class) + .defaultIdentity("guest") + .defaultCredential("guest") + .defaultProperties(defaultProperties()) + .defaultModule(VagrantComputeServiceContextModule.class); + } + + private Properties defaultProperties() { + Properties defaultProperties = BaseApiMetadata.defaultProperties(); + defaultProperties.setProperty(VagrantConstants.JCLOUDS_VAGRANT_HOME, VagrantConstants.JCLOUDS_VAGRANT_HOME_DEFAULT); + defaultProperties.put(ComputeServiceProperties.TEMPLATE, "osFamily=UBUNTU"); + return defaultProperties; + } + + @Override + public ApiMetadata build() { + return new VagrantApiMetadata(this); + } + + @Override + protected Builder self() { + return this; + } + + } +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/api/VagrantApiFacade.java ---------------------------------------------------------------------- diff --git a/vagrant/src/main/java/org/jclouds/vagrant/api/VagrantApiFacade.java b/vagrant/src/main/java/org/jclouds/vagrant/api/VagrantApiFacade.java new file mode 100644 index 0000000..c3bccfd --- /dev/null +++ b/vagrant/src/main/java/org/jclouds/vagrant/api/VagrantApiFacade.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jclouds.vagrant.api; + +import java.io.File; +import java.util.Collection; + +import org.jclouds.domain.LoginCredentials; + +public interface VagrantApiFacade<B> { + interface Factory<B> { + VagrantApiFacade<B> create(File path); + } + + /** + * Start the named machine + * + * @return the raw output of the configured provisioners + */ + String up(String machineName); + void halt(String machineName); + void destroy(String machineName); + LoginCredentials sshConfig(String machineName); + Collection<B> listBoxes(); + B getBox(String boxName); + void haltForced(String name); + boolean exists(); +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/compute/VagrantComputeServiceAdapter.java ---------------------------------------------------------------------- diff --git a/vagrant/src/main/java/org/jclouds/vagrant/compute/VagrantComputeServiceAdapter.java b/vagrant/src/main/java/org/jclouds/vagrant/compute/VagrantComputeServiceAdapter.java new file mode 100644 index 0000000..665504f --- /dev/null +++ b/vagrant/src/main/java/org/jclouds/vagrant/compute/VagrantComputeServiceAdapter.java @@ -0,0 +1,401 @@ +/* + * 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.jclouds.vagrant.compute; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.annotation.Resource; +import javax.inject.Inject; +import javax.inject.Named; + +import org.jclouds.compute.ComputeServiceAdapter; +import org.jclouds.compute.domain.Hardware; +import org.jclouds.compute.domain.Image; +import org.jclouds.compute.domain.NodeMetadata.Status; +import org.jclouds.compute.domain.OsFamily; +import org.jclouds.compute.domain.Processor; +import org.jclouds.compute.domain.Template; +import org.jclouds.compute.domain.Volume; +import org.jclouds.compute.domain.Volume.Type; +import org.jclouds.compute.util.AutomaticHardwareIdSpec; +import org.jclouds.domain.Location; +import org.jclouds.domain.LocationBuilder; +import org.jclouds.domain.LocationScope; +import org.jclouds.domain.LoginCredentials; +import org.jclouds.location.suppliers.all.JustProvider; +import org.jclouds.logging.Logger; +import org.jclouds.vagrant.api.VagrantApiFacade; +import org.jclouds.vagrant.domain.VagrantNode; +import org.jclouds.vagrant.internal.MachineConfig; +import org.jclouds.vagrant.internal.VagrantNodeRegistry; +import org.jclouds.vagrant.reference.VagrantConstants; +import org.jclouds.vagrant.util.VagrantUtils; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.base.Supplier; +import com.google.common.base.Throwables; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; + +public class VagrantComputeServiceAdapter<B> implements ComputeServiceAdapter<VagrantNode, Hardware, B, Location> { + private static final Pattern PATTERN_IP_ADDR = Pattern.compile("inet ([0-9\\.]+)/(\\d+)"); + private static final Pattern PATTERN_IPCONFIG = Pattern.compile("IPv4 Address[ .]+: ([0-9\\.]+)"); + + @Resource + protected Logger logger = Logger.NULL; + + private final File home; + private final JustProvider locationSupplier; + private final VagrantNodeRegistry nodeRegistry; + private final MachineConfig.Factory machineConfigFactory; + private final Function<Collection<B>, Collection<B>> outdatedBoxesFilter; + private final VagrantApiFacade.Factory<B> cliFactory; + private final Supplier<? extends Map<String, Hardware>> hardwareSupplier; + + @Inject + VagrantComputeServiceAdapter(@Named(VagrantConstants.JCLOUDS_VAGRANT_HOME) String home, + JustProvider locationSupplier, + VagrantNodeRegistry nodeRegistry, + MachineConfig.Factory machineConfigFactory, + Function<Collection<B>, Collection<B>> outdatedBoxesFilter, + VagrantApiFacade.Factory<B> cliFactory, + Supplier<? extends Map<String, Hardware>> hardwareSupplier) { + this.home = new File(checkNotNull(home, "home")); + this.locationSupplier = checkNotNull(locationSupplier, "locationSupplier"); + this.nodeRegistry = checkNotNull(nodeRegistry, "nodeRegistry"); + this.machineConfigFactory = checkNotNull(machineConfigFactory, "machineConfigFactory"); + this.outdatedBoxesFilter = checkNotNull(outdatedBoxesFilter, "outdatedBoxesFilter"); + this.cliFactory = checkNotNull(cliFactory, "cliFactory"); + this.hardwareSupplier = checkNotNull(hardwareSupplier, "hardwareSupplier"); + this.home.mkdirs(); + } + + @Override + public NodeAndInitialCredentials<VagrantNode> createNodeWithGroupEncodedIntoName(String group, String name, Template template) { + String machineName = removeFromStart(name, group); + File nodePath = new File(home, group); + + init(nodePath, machineName, template); + + NodeAndInitialCredentials<VagrantNode> node = startMachine(nodePath, group, machineName, template.getImage()); + nodeRegistry.add(node.getNode()); + return node; + } + + private NodeAndInitialCredentials<VagrantNode> startMachine(File path, String group, String name, Image image) { + + VagrantApiFacade<B> vagrant = cliFactory.create(path); + String rawOutput = vagrant.up(name); + String output = normalizeOutput(name, rawOutput); + + OsFamily osFamily = image.getOperatingSystem().getFamily(); + String id = group + "/" + name; + VagrantNode node = VagrantNode.builder() + .setPath(path) + .setId(id) + .setGroup(group) + .setName(name) + .setImage(image) + .setNetworks(getNetworks(output, getOsInterfacePattern(osFamily))) + .setHostname(getHostname(output)) + .build(); + node.setMachineState(Status.RUNNING); + + LoginCredentials loginCredentials = null; + if (osFamily != OsFamily.WINDOWS) { + loginCredentials = vagrant.sshConfig(name); + } + + // PrioritizeCredentialsFromTemplate will overwrite loginCredentials with image credentials + // AdaptingComputeServiceStrategies saves the merged credentials in credentialStore + return new NodeAndInitialCredentials<VagrantNode>(node, node.id(), loginCredentials); + } + + private String normalizeOutput(String name, String output) { + return output + .replaceAll("(?m)^([^,]*,){4}", "") + .replace("==> " + name + ": ", "") + // Vagrant shows some of the \n verbatim in provisioning command results. + .replace("\\n", "\n"); + } + + private Pattern getOsInterfacePattern(OsFamily osFamily) { + if (osFamily == OsFamily.WINDOWS) { + return PATTERN_IPCONFIG; + } else { + return PATTERN_IP_ADDR; + } + } + + private Collection<String> getNetworks(String output, Pattern ifPattern) { + String networks = getDelimitedString( + output, + VagrantConstants.DELIMITER_NETWORKS_START, + VagrantConstants.DELIMITER_NETWORKS_END); + Matcher m = ifPattern.matcher(networks); + Collection<String> ips = new ArrayList<String>(); + while (m.find()) { + String network = m.group(1); + // TODO figure out a more generic approach to ignore unreachable networkds (this one is the NAT'd address). + if (network.startsWith("10.")) continue; + ips.add(network); + } + return ips; + } + + private String getHostname(String output) { + return getDelimitedString( + output, + VagrantConstants.DELIMITER_HOSTNAME_START, + VagrantConstants.DELIMITER_HOSTNAME_END); + } + + private String getDelimitedString(String value, String delimStart, String delimEnd) { + int startPos = value.indexOf(delimStart); + int endPos = value.indexOf(delimEnd); + if (startPos == -1) { + throw new IllegalStateException("Delimiter " + delimStart + " not found in output \n" + value); + } + if (endPos == -1) { + throw new IllegalStateException("Delimiter " + delimEnd + " not found in output \n" + value); + } + return value.substring(startPos + delimStart.length(), endPos).trim(); + } + + private void init(File path, String name, Template template) { + try { + writeVagrantfile(path); + initMachineConfig(path, name, template); + } catch (IOException e) { + throw new IllegalStateException("Unable to initialize Vagrant configuration at " + + path + " for machine " + name, e); + } + } + + private void writeVagrantfile(File path) throws IOException { + path.mkdirs(); + VagrantUtils.write( + new File(path, VagrantConstants.VAGRANTFILE), + getClass().getClassLoader().getResourceAsStream(VagrantConstants.VAGRANTFILE)); + } + + private void initMachineConfig(File path, String name, Template template) { + MachineConfig config = machineConfigFactory.newInstance(path, name); + List<? extends Volume> volumes = template.getHardware().getVolumes(); + if (volumes != null) { + if (volumes.size() == 1) { + Volume volume = Iterables.getOnlyElement(volumes); + if (volume.getType() != Type.LOCAL || volume.getSize() != null) { + throw new IllegalStateException("Custom volume settings not supported. Volumes required: " + volumes); + } + } else if (volumes.size() > 1) { + throw new IllegalStateException("Custom volume settings not supported. Volumes required: " + volumes); + } + } + config.save(ImmutableMap.<String, Object>of( + VagrantConstants.CONFIG_BOX, template.getImage().getName(), + VagrantConstants.CONFIG_OS_FAMILY, template.getImage().getOperatingSystem().getFamily(), + VagrantConstants.CONFIG_HARDWARE_ID, getHardwareId(template), + VagrantConstants.CONFIG_MEMORY, Integer.toString(template.getHardware().getRam()), + VagrantConstants.CONFIG_CPUS, Integer.toString(countProcessors(template)))); + } + + private String getHardwareId(Template template) { + String id = template.getHardware().getId(); + if (AutomaticHardwareIdSpec.isAutomaticId(id)) { + return VagrantConstants.MACHINES_AUTO_HARDWARE; + } else { + return id; + } + } + + private int countProcessors(Template template) { + int cnt = 0; + for (Processor p : template.getHardware().getProcessors()) { + cnt += p.getCores(); + } + return cnt; + } + + @Override + public Iterable<Hardware> listHardwareProfiles() { + return hardwareSupplier.get().values(); + } + + @Override + public Iterable<B> listImages() { + Collection<B> allBoxes = cliFactory.create(new File(".")).listBoxes(); + return outdatedBoxesFilter.apply(allBoxes); + } + + @Override + public B getImage(String id) { + return cliFactory.create(new File(".")).getBox(id); + } + + @Override + public Iterable<Location> listLocations() { + Location provider = Iterables.getOnlyElement(locationSupplier.get()); + return ImmutableList.of( + new LocationBuilder().id("localhost").description("localhost").parent(provider).scope(LocationScope.HOST).build()); + } + + @Override + public VagrantNode getNode(String id) { + // needed for BaseComputeServiceLiveTest.testAScriptExecutionAfterBootWithBasicTemplate() + // waits for the thread updating the credentialStore to execute + try { + Thread.sleep(200); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw Throwables.propagate(e); + } + + return nodeRegistry.get(id); + } + + @Override + public void destroyNode(String id) { + VagrantNode node = nodeRegistry.get(id); + node.setMachineState(Status.TERMINATED); + getMachine(node).destroy(node.name()); + nodeRegistry.onTerminated(node); + deleteMachine(node); + } + + private void deleteMachine(VagrantNode node) { + File nodeFolder = node.path(); + File machinesFolder = new File(nodeFolder, VagrantConstants.MACHINES_CONFIG_SUBFOLDER); + String filePattern = node.name() + "."; + logger.debug("Deleting machine %s", node.id()); + VagrantUtils.deleteFiles(machinesFolder, filePattern); + // No more machines in this group, remove everything + if (machinesFolder.list().length == 0) { + logger.debug("Machine %s is last in group, deleting Vagrant folder %s", node.id(), nodeFolder.getAbsolutePath()); + VagrantUtils.deleteFolder(nodeFolder); + } + } + + @Override + public void rebootNode(String id) { + halt(id); + + VagrantNode node = nodeRegistry.get(id); + String name = node.name(); + VagrantApiFacade<B> vagrant = getMachine(node); + vagrant.up(name); + } + + private void halt(String id) { + VagrantNode node = nodeRegistry.get(id); + String name = node.name(); + VagrantApiFacade<B> vagrant = getMachine(node); + + try { + vagrant.halt(name); + } catch (IllegalStateException e) { + logger.warn(e, "Failed graceful shutdown of machine " + id + ". Will try to halt it forcefully instead."); + vagrant.haltForced(name); + } + } + + @Override + public void resumeNode(String id) { + VagrantNode node = nodeRegistry.get(id); + String name = node.name(); + VagrantApiFacade<B> vagrant = getMachine(node); + vagrant.up(name); + node.setMachineState(Status.RUNNING); + } + + @Override + public void suspendNode(String id) { + halt(id); + VagrantNode node = nodeRegistry.get(id); + node.setMachineState(Status.SUSPENDED); + } + + @Override + public Iterable<VagrantNode> listNodes() { + return FluentIterable.from(Arrays.asList(home.listFiles())) + .transformAndConcat(new Function<File, Collection<VagrantNode>>() { + @Override + public Collection<VagrantNode> apply(File input) { + File machines = new File(input, VagrantConstants.MACHINES_CONFIG_SUBFOLDER); + VagrantApiFacade<B> vagrant = cliFactory.create(input); + if (input.isDirectory() && machines.exists() && vagrant.exists()) { + Collection<VagrantNode> nodes = new ArrayList<VagrantNode>(); + for (File machine : machines.listFiles()) { + if (machine.getName().endsWith(VagrantConstants.MACHINES_CONFIG_EXTENSION)) { + String id = input.getName() + "/" + machine.getName().replace(VagrantConstants.MACHINES_CONFIG_EXTENSION, ""); + VagrantNode n = nodeRegistry.get(id); + if (n != null) { + nodes.add(n); + } + } + } + return nodes; + } else { + return ImmutableList.of(); + } + } + }); + } + + @Override + public Iterable<VagrantNode> listNodesByIds(final Iterable<String> ids) { + return Iterables.filter(listNodes(), new Predicate<VagrantNode>() { + @Override + public boolean apply(VagrantNode input) { + return Iterables.contains(ids, input.id()); + } + }); + } + + private VagrantApiFacade<B> getMachine(VagrantNode node) { + File nodePath = node.path(); + return cliFactory.create(nodePath); + } + + private String removeFromStart(String name, String group) { + if (name.startsWith(group)) { + String machineName = name.substring(group.length()); + // Can't pass names starting with dash on the command line + if (machineName.startsWith("-")) { + return machineName.substring(1); + } else { + return machineName; + } + } else { + return name; + } + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/config/PersistVagrantCredentialsModule.java ---------------------------------------------------------------------- diff --git a/vagrant/src/main/java/org/jclouds/vagrant/config/PersistVagrantCredentialsModule.java b/vagrant/src/main/java/org/jclouds/vagrant/config/PersistVagrantCredentialsModule.java new file mode 100644 index 0000000..d140b76 --- /dev/null +++ b/vagrant/src/main/java/org/jclouds/vagrant/config/PersistVagrantCredentialsModule.java @@ -0,0 +1,148 @@ +/* + * 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.jclouds.vagrant.config; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.io.File; +import java.io.IOException; +import java.util.Map; + +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.domain.NodeMetadataBuilder; +import org.jclouds.compute.internal.PersistNodeCredentials; +import org.jclouds.domain.Credentials; +import org.jclouds.domain.LoginCredentials; +import org.jclouds.javax.annotation.Nullable; +import org.jclouds.scriptbuilder.domain.Statement; +import org.jclouds.scriptbuilder.functions.CredentialsFromAdminAccess; +import org.jclouds.vagrant.domain.VagrantNode; +import org.jclouds.vagrant.internal.MachineConfig; +import org.jclouds.vagrant.internal.VagrantNodeRegistry; +import org.jclouds.vagrant.internal.MachineConfig.Factory; +import org.jclouds.vagrant.reference.VagrantConstants; +import org.jclouds.vagrant.util.VagrantUtils; + +import com.google.common.base.Function; +import com.google.inject.AbstractModule; +import com.google.inject.Inject; +import com.google.inject.TypeLiteral; +import com.google.inject.assistedinject.Assisted; +import com.google.inject.assistedinject.FactoryModuleBuilder; +import com.google.inject.name.Names; + +public class PersistVagrantCredentialsModule extends AbstractModule { + + static class RefreshCredentialsForNodeIfRanAdminAccess implements Function<NodeMetadata, NodeMetadata> { + protected final Map<String, Credentials> credentialStore; + protected final VagrantNodeRegistry vagrantNodeRegistry; + protected final Statement statement; + protected final Factory machineConfigFactory; + + @Inject + RefreshCredentialsForNodeIfRanAdminAccess( + VagrantNodeRegistry vagrantNodeRegistry, + Map<String, Credentials> credentialStore, + @Nullable @Assisted Statement statement, + MachineConfig.Factory machineConfigFactory) { + this.vagrantNodeRegistry = checkNotNull(vagrantNodeRegistry, "vagrantNodeRegistry"); + this.credentialStore = checkNotNull(credentialStore, "credentialStore"); + this.statement = statement; + this.machineConfigFactory = checkNotNull(machineConfigFactory, "machineConfigFactory"); + } + + @Override + public NodeMetadata apply(NodeMetadata input) { + if (statement == null) + return input; + Credentials credentials = CredentialsFromAdminAccess.INSTANCE.apply(statement); + if (credentials != null) { + LoginCredentials creds = LoginCredentials.fromCredentials(credentials); + input = NodeMetadataBuilder.fromNodeMetadata(input).credentials(creds).build(); + credentialStore.put("node#" + input.getId(), input.getCredentials()); + updateMachine(input.getId(), creds); + } + return input; + } + + protected void updateMachine(String id, LoginCredentials credentials) { + VagrantNode node = vagrantNodeRegistry.get(id); + if (node == null) { + throw new IllegalStateException("Updating node credentials failed because node " + id + " not found."); + } + String provider = node.image().getUserMetadata().get(VagrantConstants.USER_META_PROVIDER); + + MachineConfig machineConfig = machineConfigFactory.newInstance(node); + Map<String, Object> config = machineConfig.load(); + + config.put(VagrantConstants.CONFIG_USERNAME, credentials.getUser()); + config.remove(VagrantConstants.CONFIG_PASSWORD); + if (credentials.getOptionalPassword().isPresent()) { + config.put(VagrantConstants.CONFIG_PASSWORD, credentials.getOptionalPassword().get()); + } + if (credentials.getOptionalPrivateKey().isPresent()) { + // Overwrite existing private key and dont't use config.ssh.private_key_path - doesn't work, is ignored. + File privateKeyFile = new File(node.path(), ".vagrant/machines/" + node.name() + "/" + provider + "/private_key"); + try { + VagrantUtils.write(privateKeyFile, credentials.getOptionalPrivateKey().get()); + } catch (IOException e) { + throw new IllegalStateException("Failure updating credentials for " + id + + ". Can't save private key to " + privateKeyFile.getAbsolutePath(), e); + } + } + + machineConfig.save(config); + } + } + + static class RefreshCredentialsForNode extends RefreshCredentialsForNodeIfRanAdminAccess { + + @Inject + RefreshCredentialsForNode( + VagrantNodeRegistry vagrantNodeRegistry, + Map<String, Credentials> credentialStore, + @Assisted @Nullable Statement statement, + MachineConfig.Factory machineConfigFactory) { + super(vagrantNodeRegistry, credentialStore, statement, machineConfigFactory); + } + + @Override + public NodeMetadata apply(NodeMetadata input) { + input = super.apply(input); + if (input.getCredentials() != null) { + credentialStore.put("node#" + input.getId(), input.getCredentials()); + updateMachine(input.getId(), input.getCredentials()); + } + return input; + } + + } + + + @Override + protected void configure() { + install(new FactoryModuleBuilder() + .implement(new TypeLiteral<Function<NodeMetadata, NodeMetadata>>() {}, + Names.named("ifAdminAccess"), + RefreshCredentialsForNodeIfRanAdminAccess.class) + .implement(new TypeLiteral<Function<NodeMetadata, NodeMetadata>>() {}, + Names.named("always"), + RefreshCredentialsForNode.class) + .build(PersistNodeCredentials.class)); + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/config/VagrantComputeServiceContextModule.java ---------------------------------------------------------------------- diff --git a/vagrant/src/main/java/org/jclouds/vagrant/config/VagrantComputeServiceContextModule.java b/vagrant/src/main/java/org/jclouds/vagrant/config/VagrantComputeServiceContextModule.java new file mode 100644 index 0000000..61f30fd --- /dev/null +++ b/vagrant/src/main/java/org/jclouds/vagrant/config/VagrantComputeServiceContextModule.java @@ -0,0 +1,109 @@ +/* + * 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.jclouds.vagrant.config; + +import java.util.Collection; +import java.util.Map; + +import org.jclouds.compute.ComputeServiceAdapter; +import org.jclouds.compute.config.ComputeServiceAdapterContextModule; +import org.jclouds.compute.config.PersistNodeCredentialsModule; +import org.jclouds.compute.domain.Hardware; +import org.jclouds.compute.domain.Image; +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.domain.internal.ArbitraryCpuRamTemplateBuilderImpl; +import org.jclouds.compute.domain.internal.TemplateBuilderImpl; +import org.jclouds.compute.strategy.PopulateDefaultLoginCredentialsForImageStrategy; +import org.jclouds.date.TimeStamp; +import org.jclouds.domain.Location; +import org.jclouds.functions.IdentityFunction; +import org.jclouds.vagrant.api.VagrantApiFacade; +import org.jclouds.vagrant.compute.VagrantComputeServiceAdapter; +import org.jclouds.vagrant.domain.VagrantNode; +import org.jclouds.vagrant.functions.BoxToImage; +import org.jclouds.vagrant.functions.MachineToNodeMetadata; +import org.jclouds.vagrant.functions.OutdatedBoxesFilter; +import org.jclouds.vagrant.internal.VagrantCliFacade; +import org.jclouds.vagrant.internal.VagrantWireLogger; +import org.jclouds.vagrant.strategy.VagrantDefaultImageCredentials; +import org.jclouds.vagrant.suppliers.VagrantHardwareSupplier; + +import com.google.common.base.Function; +import com.google.common.base.Supplier; +import com.google.inject.Module; +import com.google.inject.Provides; +import com.google.inject.Singleton; +import com.google.inject.TypeLiteral; +import com.google.inject.assistedinject.FactoryModuleBuilder; + +import vagrant.api.CommandIOListener; +import vagrant.api.domain.Box; + +public class VagrantComputeServiceContextModule extends ComputeServiceAdapterContextModule<VagrantNode, Hardware, Box, Location> { + + @Override + protected void configure() { + super.configure(); + bind(new TypeLiteral<ComputeServiceAdapter<VagrantNode, Hardware, Box, Location>>() { + }).to(new TypeLiteral<VagrantComputeServiceAdapter<Box>>() {}); + bind(new TypeLiteral<Function<VagrantNode, NodeMetadata>>() { + }).to(MachineToNodeMetadata.class); + bind(new TypeLiteral<Function<Box, Image>>() { + }).to(BoxToImage.class); + bind(new TypeLiteral<Supplier<? extends Map<String, Hardware>>>() { + }).to(VagrantHardwareSupplier.class).in(Singleton.class); + bind(new TypeLiteral<Function<Hardware, Hardware>>() { + }).to(this.<Hardware>castIdentityFunction()); + bind(new TypeLiteral<Function<Location, Location>>() { + }).to(this.<Location>castIdentityFunction()); + bind(new TypeLiteral<Function<Collection<Box>, Collection<Box>>>() { + }).to(OutdatedBoxesFilter.class); + install(new FactoryModuleBuilder() + .implement(new TypeLiteral<VagrantApiFacade<Box>>() {}, VagrantCliFacade.class) + .build(new TypeLiteral<VagrantApiFacade.Factory<Box>>() {})); + bind(PopulateDefaultLoginCredentialsForImageStrategy.class).to(VagrantDefaultImageCredentials.class); + bind(TemplateBuilderImpl.class).to(ArbitraryCpuRamTemplateBuilderImpl.class); + bind(CommandIOListener.class).to(VagrantWireLogger.class).in(Singleton.class); + } + + @Provides + @TimeStamp + public Supplier<Long> timeSupplier() { + return new Supplier<Long>() { + @Override + public Long get() { + return System.currentTimeMillis(); + } + }; + } + + @Override + protected void install(Module module) { + // override PersistNodeCredentialsModule bindings, any better way to do it? + if (module instanceof PersistNodeCredentialsModule) { + super.install(new PersistVagrantCredentialsModule()); + } else { + super.install(module); + } + } + + @SuppressWarnings("unchecked") + private <T> Class<Function<T, T>> castIdentityFunction() { + return (Class<Function<T, T>>)(Class<?>)IdentityFunction.class; + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/domain/VagrantNode.java ---------------------------------------------------------------------- diff --git a/vagrant/src/main/java/org/jclouds/vagrant/domain/VagrantNode.java b/vagrant/src/main/java/org/jclouds/vagrant/domain/VagrantNode.java new file mode 100644 index 0000000..cfb02ca --- /dev/null +++ b/vagrant/src/main/java/org/jclouds/vagrant/domain/VagrantNode.java @@ -0,0 +1,70 @@ +/* + * 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.jclouds.vagrant.domain; + +import java.io.File; +import java.util.Collection; + +import org.jclouds.compute.domain.Image; +import org.jclouds.compute.domain.NodeMetadata.Status; + +import com.google.auto.value.AutoValue; + +@AutoValue +public abstract class VagrantNode { + + private volatile Status machineState = Status.PENDING; + + public abstract File path(); + + public abstract String id(); + + public abstract String group(); + + public abstract String name(); + + public abstract Image image(); + + public abstract Collection<String> networks(); + + public abstract String hostname(); + + public static Builder builder() { + return new AutoValue_VagrantNode.Builder(); + } + + @AutoValue.Builder + public abstract static class Builder { + public abstract Builder setPath(File path); + public abstract Builder setId(String id); + public abstract Builder setGroup(String group); + public abstract Builder setName(String name); + public abstract Builder setImage(Image image); + public abstract Builder setNetworks(Collection<String> networks); + public abstract Builder setHostname(String hostname); + public abstract VagrantNode build(); + } + + public Status machineState() { + return machineState; + } + + public void setMachineState(Status machineState) { + this.machineState = machineState; + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/functions/BoxToImage.java ---------------------------------------------------------------------- diff --git a/vagrant/src/main/java/org/jclouds/vagrant/functions/BoxToImage.java b/vagrant/src/main/java/org/jclouds/vagrant/functions/BoxToImage.java new file mode 100644 index 0000000..1fd77fa --- /dev/null +++ b/vagrant/src/main/java/org/jclouds/vagrant/functions/BoxToImage.java @@ -0,0 +1,75 @@ +/* + * 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.jclouds.vagrant.functions; + +import static com.google.common.base.Preconditions.checkNotNull; + +import org.jclouds.compute.domain.Image; +import org.jclouds.compute.domain.Image.Status; +import org.jclouds.compute.domain.ImageBuilder; +import org.jclouds.compute.domain.OperatingSystem; +import org.jclouds.compute.domain.OsFamily; +import org.jclouds.vagrant.internal.BoxConfig; +import org.jclouds.vagrant.reference.VagrantConstants; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableMap; +import com.google.inject.Inject; + +import vagrant.api.domain.Box; + +public class BoxToImage implements Function<Box, Image> { + private BoxConfig.Factory boxConfigFactory; + + @Inject + BoxToImage(BoxConfig.Factory boxConfigFactory) { + this.boxConfigFactory = checkNotNull(boxConfigFactory, "boxConfigFactory"); + } + + @Override + public Image apply(Box input) { + OperatingSystem os = new OperatingSystem(inferOsFamily(input), input.getName(), input.getVersion(), null, input.getName(), true); + return new ImageBuilder() + .ids(input.getName()) + .name(input.getName()) + .version(input.getVersion()) + .operatingSystem(os) + .status(Status.AVAILABLE) + // Overriden by AddDefaultCredentialsToImage + //.defaultCredentials() + .userMetadata(ImmutableMap.of(VagrantConstants.USER_META_PROVIDER, input.getProvider())) + .build(); + } + + private OsFamily inferOsFamily(Box input) { + String name = input.getName().toUpperCase(); + for (OsFamily family : OsFamily.values()) { + if (name.contains(family.name())) { + return family; + } + } + + BoxConfig configParser = boxConfigFactory.newInstance(input); + Optional<String> guest = configParser.getKey(VagrantConstants.KEY_VM_GUEST); + if (guest.isPresent() && guest.get().equals(VagrantConstants.VM_GUEST_WINDOWS)) { + return OsFamily.WINDOWS; + } + return OsFamily.UNRECOGNIZED; + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/functions/MachineToNodeMetadata.java ---------------------------------------------------------------------- diff --git a/vagrant/src/main/java/org/jclouds/vagrant/functions/MachineToNodeMetadata.java b/vagrant/src/main/java/org/jclouds/vagrant/functions/MachineToNodeMetadata.java new file mode 100644 index 0000000..298b7cc --- /dev/null +++ b/vagrant/src/main/java/org/jclouds/vagrant/functions/MachineToNodeMetadata.java @@ -0,0 +1,120 @@ +/* + * 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.jclouds.vagrant.functions; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Map; +import java.util.Set; + +import org.jclouds.collect.Memoized; +import org.jclouds.compute.domain.Hardware; +import org.jclouds.compute.domain.HardwareBuilder; +import org.jclouds.compute.domain.Image; +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.domain.NodeMetadataBuilder; +import org.jclouds.compute.domain.OsFamily; +import org.jclouds.compute.domain.Processor; +import org.jclouds.compute.util.AutomaticHardwareIdSpec; +import org.jclouds.domain.Location; +import org.jclouds.vagrant.domain.VagrantNode; +import org.jclouds.vagrant.internal.BoxConfig; +import org.jclouds.vagrant.internal.MachineConfig; +import org.jclouds.vagrant.reference.VagrantConstants; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.inject.Inject; + +public class MachineToNodeMetadata implements Function<VagrantNode, NodeMetadata> { + private final Location location; + private final BoxConfig.Factory boxConfigFactory; + private final MachineConfig.Factory machineConfigFactory; + private final Supplier<? extends Map<String, Hardware>> hardwareSupplier; + + @Inject + MachineToNodeMetadata( + @Memoized Supplier<Set<? extends Location>> locations, + BoxConfig.Factory boxConfigFactory, + MachineConfig.Factory machineConfigFactory, + Supplier<? extends Map<String, Hardware>> hardwareSupplier) { + this.location = Iterables.getOnlyElement(checkNotNull(locations, "locations").get()); + this.boxConfigFactory = checkNotNull(boxConfigFactory, "boxConfigFactory"); + this.machineConfigFactory = checkNotNull(machineConfigFactory, "machineConfigFactory"); + this.hardwareSupplier = checkNotNull(hardwareSupplier, "hardwareSupplier"); + } + + @Override + public NodeMetadata apply(VagrantNode node) { + NodeMetadataBuilder nodeMetadataBuilder = new NodeMetadataBuilder() + .ids(node.id()) + .name(node.name()) + .group(node.group()) + .imageId(node.image().getId()) + .location(location) + .hardware(getHardware(node)) + .operatingSystem(node.image().getOperatingSystem()) + .hostname(node.name()) + .status(node.machineState()) + .loginPort(getLoginPort(node.image())) + .privateAddresses(node.networks()) + .publicAddresses(ImmutableList.<String> of()) + .hostname(node.hostname()); + // Credentials fetched from cache from AdaptingComputeServiceStrategies.addLoginCredentials. + // Cache already initialized just after creating the node. + return nodeMetadataBuilder.build(); + } + + private Hardware getHardware(VagrantNode node) { + MachineConfig machineConfig = machineConfigFactory.newInstance(node); + + Map<String, Object> config = machineConfig.load(); + String hardwareId = config.get(VagrantConstants.CONFIG_HARDWARE_ID).toString(); + if (hardwareId.equals(VagrantConstants.MACHINES_AUTO_HARDWARE)) { + double cpus = Double.parseDouble(config.get(VagrantConstants.CONFIG_CPUS).toString()); + int memory = Integer.parseInt(config.get(VagrantConstants.CONFIG_MEMORY).toString()); + AutomaticHardwareIdSpec hardwareSpec = AutomaticHardwareIdSpec.automaticHardwareIdSpecBuilder(cpus, memory, Optional.<Float>absent()); + return new HardwareBuilder() + .id(hardwareSpec.toString()) + .providerId(hardwareSpec.toString()) + .processor(new Processor(cpus, 1.0)) + .ram(memory) + .build(); + } else { + Hardware hardware = hardwareSupplier.get().get(hardwareId); + if (hardware == null) { + throw new IllegalStateException("Unsupported hardwareId " + hardwareId + " for machine " + node.id()); + } + return hardware; + } + } + + private int getLoginPort(Image image) { + BoxConfig config = boxConfigFactory.newInstance(image); + String port; + if (image.getOperatingSystem().getFamily() == OsFamily.WINDOWS) { + port = config.getKey(VagrantConstants.KEY_WINRM_PORT).or("5985"); + } else { + port = config.getKey(VagrantConstants.KEY_SSH_PORT).or("22"); + } + return Integer.parseInt(port); + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/functions/OutdatedBoxesFilter.java ---------------------------------------------------------------------- diff --git a/vagrant/src/main/java/org/jclouds/vagrant/functions/OutdatedBoxesFilter.java b/vagrant/src/main/java/org/jclouds/vagrant/functions/OutdatedBoxesFilter.java new file mode 100644 index 0000000..83bfd80 --- /dev/null +++ b/vagrant/src/main/java/org/jclouds/vagrant/functions/OutdatedBoxesFilter.java @@ -0,0 +1,69 @@ +/* + * 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.jclouds.vagrant.functions; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Map; + +import com.google.common.base.Function; +import com.google.common.base.Splitter; +import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; +import com.google.common.collect.Ordering; + +import vagrant.api.domain.Box; + +public class OutdatedBoxesFilter implements Function<Collection<Box>, Collection<Box>> { + private static final Comparator<Box> VERSION_COMPARATOR = new Comparator<Box>() { + private final Ordering<Iterable<Comparable<?>>> LEXI_COMPARATOR = Ordering.natural().lexicographical(); + private final Function<String, Comparable<?>> NUMBER_PARSER = new Function<String, Comparable<?>>() { + @Override + public Comparable<?> apply(String input) { + try { + return Integer.parseInt(input); + } catch (NumberFormatException e) { + return input; + } + } + }; + + @Override + public int compare(Box o1, Box o2) { + int nameCompare = o1.getName().compareTo(o2.getName()); + if (nameCompare != 0) return nameCompare; + + Iterable<Comparable<?>> v1 = Iterables.transform(Splitter.on('.').split(o1.getVersion()), NUMBER_PARSER); + Iterable<Comparable<?>> v2 = Iterables.transform(Splitter.on('.').split(o2.getVersion()), NUMBER_PARSER); + return LEXI_COMPARATOR.compare(v1, v2); + } + }; + + @Override + public Collection<Box> apply(Collection<Box> input) { + ArrayList<Box> sorted = new ArrayList<Box>(input); + Collections.sort(sorted, VERSION_COMPARATOR); + Map<String, Box> boxes = Maps.newHashMap(); + for (Box box : sorted) { + boxes.put(box.getName(), box); + } + return boxes.values(); + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/internal/BoxConfig.java ---------------------------------------------------------------------- diff --git a/vagrant/src/main/java/org/jclouds/vagrant/internal/BoxConfig.java b/vagrant/src/main/java/org/jclouds/vagrant/internal/BoxConfig.java new file mode 100644 index 0000000..f5ceef2 --- /dev/null +++ b/vagrant/src/main/java/org/jclouds/vagrant/internal/BoxConfig.java @@ -0,0 +1,110 @@ +/* + * 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.jclouds.vagrant.internal; + +import java.io.File; +import java.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.jclouds.compute.domain.Image; +import org.jclouds.vagrant.reference.VagrantConstants; + +import com.google.common.base.Charsets; +import com.google.common.base.Optional; +import com.google.common.io.Files; + +import vagrant.api.domain.Box; + +public class BoxConfig { + public static class Factory { + public BoxConfig newInstance(Image image) { + String provider = image.getUserMetadata().get(VagrantConstants.USER_META_PROVIDER); + return new BoxConfig(getVagrantHome(), image.getName(), image.getVersion(), provider); + } + + public BoxConfig newInstance(File vagrantHome, Image image) { + return this.newInstance(getVagrantHome(), image); + } + + public BoxConfig newInstance(Box box) { + return this.newInstance(getVagrantHome(), box); + } + + public BoxConfig newInstance(File vagrantHome, Box box) { + return new BoxConfig(vagrantHome, box.getName(), box.getVersion(), box.getProvider()); + } + + private File getVagrantHome() { + Optional<String> home = Optional.fromNullable(System.getenv(VagrantConstants.ENV_VAGRANT_HOME)); + return new File(home.or(VagrantConstants.ENV_VAGRANT_HOME_DEFAULT)); + } + + } + + private String config; + private File providerPath; + + protected BoxConfig(File vagrantHome, String name, String version, String provider) { + File boxes = new File(vagrantHome, VagrantConstants.VAGRANT_BOXES_SUBFOLDER); + File boxPath = new File(boxes, name.replace("/", VagrantConstants.ESCAPE_SLASH)); + File versionPath = new File(boxPath, version); + File providerPath = new File(versionPath, provider); + File vagrantfilePath = new File(providerPath, VagrantConstants.VAGRANTFILE); + + if (!vagrantfilePath.exists()) { + throw new IllegalStateException("Vagrantfile for box '" + name + "'" + + " at " + vagrantfilePath.getAbsolutePath() + " not found"); + } + + try { + config = Files.toString(vagrantfilePath, Charsets.UTF_8); + } catch (IOException e) { + throw new IllegalStateException("Failure reading box '" + name + "'" + + " at " + vagrantfilePath.getAbsolutePath(), e); + } + + this.providerPath = providerPath; + } + + public File getFolder() { + return providerPath; + } + + public Optional<String> getKey(String key) { + String keyQuoted = Pattern.quote(key); + String search = keyQuoted + "\\s*=\\s*(.*)"; + Matcher matcher = Pattern.compile(search).matcher(config); + if (matcher.find()) { + return Optional.of(matcher.group(1).trim()); + } else { + return Optional.absent(); + } + } + + public Optional<String> getStringKey(String key) { + String keyQuoted = Pattern.quote(key); + String search = keyQuoted + "\\s*=\\s*\"(.*)\""; + Matcher matcher = Pattern.compile(search).matcher(config); + if (matcher.find()) { + return Optional.of(matcher.group(1)); + } else { + return Optional.absent(); + } + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/internal/MachineConfig.java ---------------------------------------------------------------------- diff --git a/vagrant/src/main/java/org/jclouds/vagrant/internal/MachineConfig.java b/vagrant/src/main/java/org/jclouds/vagrant/internal/MachineConfig.java new file mode 100644 index 0000000..8f23d87 --- /dev/null +++ b/vagrant/src/main/java/org/jclouds/vagrant/internal/MachineConfig.java @@ -0,0 +1,116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jclouds.vagrant.internal; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Properties; + +import org.jclouds.JcloudsVersion; +import org.jclouds.util.Closeables2; +import org.jclouds.vagrant.domain.VagrantNode; +import org.jclouds.vagrant.reference.VagrantConstants; + +import com.google.common.base.Charsets; +import com.google.common.base.Joiner; +import com.google.common.base.Predicates; +import com.google.common.collect.Maps; + +public class MachineConfig { + public static class Factory { + public MachineConfig newInstance(File path, String name) { + return new MachineConfig(path, name); + } + + public MachineConfig newInstance(VagrantNode node) { + return newInstance(node.path(), node.name()); + } + } + + private File configPath; + + protected MachineConfig(File path, String name) { + this.configPath = new File(new File(path, VagrantConstants.MACHINES_CONFIG_SUBFOLDER), name + ".yaml"); + } + + public Map<String, Object> load() { + Map<String, Object> config = new LinkedHashMap<String, Object>(); + Properties yaml = new Properties(); + FileInputStream fileIn; + try { + fileIn = new FileInputStream(configPath); + } catch (FileNotFoundException e) { + throw new IllegalStateException("Machine config not found: " + configPath.getAbsolutePath(), e); + } + Reader in = new InputStreamReader(fileIn, Charsets.UTF_8); + try { + // Poor man's YAML parser. It's controlled content, generated by us - not coming from a user so it's fine. + yaml.load(in); + } catch (IOException e) { + throw new IllegalStateException("Failed loading machine config " + configPath.getAbsolutePath(), e); + } finally { + Closeables2.closeQuietly(in); + } + for (String key : yaml.stringPropertyNames()) { + config.put(key, yaml.getProperty(key)); + } + return config; + } + + // Write the config ad-hoc, imitating yaml which can be read by ruby + // Could pull in snakeyaml to be more resilient to edge-cases in values + // Alternatively use JSON if jclouds already depends on it in core + public void save(Map<String, Object> config) { + File parent = configPath.getParentFile(); + if (!parent.exists() && !parent.mkdirs()) { + if (!parent.exists()) { + throw new IllegalStateException("Failure creating folder " + parent.getAbsolutePath()); + } + } + + Map<String, Object> configWithoutVersion = Maps.filterKeys(config, + Predicates.not(Predicates.equalTo(VagrantConstants.CONFIG_JCLOUDS_VERSION))); + String version = VagrantConstants.CONFIG_JCLOUDS_VERSION + ": " + JcloudsVersion.get().toString() + "\n"; + String output = version + Joiner.on("\n").withKeyValueSeparator(": ").join(configWithoutVersion); + + FileOutputStream fileOut = null; + BufferedWriter out = null; + + try { + fileOut = new FileOutputStream(configPath); + out = new BufferedWriter(new OutputStreamWriter(fileOut, Charsets.UTF_8)); + out.write(output); + } catch (IOException e) { + throw new IllegalStateException("Failed writing to machine config file " + configPath.getAbsolutePath(), e); + } finally { + if (out != null) { + Closeables2.closeQuietly(out); + } else if (fileOut != null) { + Closeables2.closeQuietly(fileOut); + } + } + } +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantCliFacade.java ---------------------------------------------------------------------- diff --git a/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantCliFacade.java b/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantCliFacade.java new file mode 100644 index 0000000..26d592f --- /dev/null +++ b/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantCliFacade.java @@ -0,0 +1,109 @@ +/* + * 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.jclouds.vagrant.internal; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.Collection; + +import javax.inject.Inject; + +import org.jclouds.domain.LoginCredentials; +import org.jclouds.vagrant.api.VagrantApiFacade; + +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.google.common.io.Files; +import com.google.inject.assistedinject.Assisted; + +import vagrant.Vagrant; +import vagrant.api.CommandIOListener; +import vagrant.api.VagrantApi; +import vagrant.api.domain.Box; +import vagrant.api.domain.SshConfig; + +public class VagrantCliFacade implements VagrantApiFacade<Box> { + private final VagrantApi vagrant; + private final VagrantOutputRecorder outputRecorder; + + @Inject + VagrantCliFacade(CommandIOListener wireLogger, @Assisted File path) { + this.outputRecorder = new VagrantOutputRecorder(checkNotNull(wireLogger, "wireLogger")); + this.vagrant = Vagrant.forPath(path, outputRecorder); + } + + @Override + public String up(String machineName) { + outputRecorder.record(); + vagrant.up(machineName); + return outputRecorder.stopRecording(); + } + + @Override + public void halt(String machineName) { + vagrant.halt(machineName); + } + + @Override + public void destroy(String machineName) { + vagrant.destroy(machineName); + } + + @Override + public LoginCredentials sshConfig(String machineName) { + SshConfig sshConfig = vagrant.sshConfig(machineName); + LoginCredentials.Builder loginCredentialsBuilder = LoginCredentials.builder() + .user(sshConfig.getUser()); + try { + String privateKey = Files.toString(new File(sshConfig.getIdentityFile()), Charset.defaultCharset()); + loginCredentialsBuilder.privateKey(privateKey); + } catch (IOException e) { + throw new IllegalStateException("Invalid private key " + sshConfig.getIdentityFile(), e); + } + + return loginCredentialsBuilder.build(); + } + + @Override + public Collection<Box> listBoxes() { + return vagrant.box().list(); + } + + @Override + public Box getBox(final String boxName) { + return Iterables.find(listBoxes(), new Predicate<Box>() { + @Override + public boolean apply(Box input) { + return boxName.equals(input.getName()); + } + }, null); + } + + @Override + public void haltForced(String name) { + vagrant.haltForced(name); + } + + @Override + public boolean exists() { + return vagrant.exists(); + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantNodeRegistry.java ---------------------------------------------------------------------- diff --git a/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantNodeRegistry.java b/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantNodeRegistry.java new file mode 100644 index 0000000..e4121fd --- /dev/null +++ b/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantNodeRegistry.java @@ -0,0 +1,112 @@ +/* + * 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.jclouds.vagrant.internal; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.DelayQueue; +import java.util.concurrent.Delayed; +import java.util.concurrent.TimeUnit; + +import org.jclouds.date.TimeStamp; +import org.jclouds.vagrant.domain.VagrantNode; + +import com.google.common.base.Supplier; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +@Singleton +public class VagrantNodeRegistry { + private static final long TERMINATED_NODES_EXPIRY_MS = TimeUnit.MINUTES.toMillis(5); + private static final long VACUUM_PERIOD_MS = TimeUnit.SECONDS.toMillis(15); + + private static class TerminatedNode implements Delayed { + Supplier<Long> timeSupplier; + long expiryTime; + VagrantNode node; + + TerminatedNode(VagrantNode node, Supplier<Long> timeSupplier) { + this.expiryTime = System.currentTimeMillis() + TERMINATED_NODES_EXPIRY_MS; + this.node = node; + this.timeSupplier = timeSupplier; + } + @Override + public int compareTo(Delayed o) { + if (this == o) { + return 0; + } else if (o instanceof TerminatedNode) { + TerminatedNode other = (TerminatedNode)o; + if (expiryTime < other.expiryTime) { + return -1; + } else if (expiryTime > other.expiryTime) { + return 1; + } else { + return 0; + } + } else { + long diff = getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS); + if (diff < 0) { + return -1; + } else if (diff > 0) { + return 1; + } else { + return 0; + } + } + } + @Override + public long getDelay(TimeUnit unit) { + return unit.convert(expiryTime - timeSupplier.get(), TimeUnit.MILLISECONDS); + } + } + private Map<String, VagrantNode> nodes = new ConcurrentHashMap<String, VagrantNode>(); + private DelayQueue<TerminatedNode> terminatedNodes = new DelayQueue<TerminatedNode>(); + + private volatile long lastVacuumMs; + private Supplier<Long> timeSupplier; + + @Inject + VagrantNodeRegistry(@TimeStamp Supplier<Long> timeSupplier) { + this.timeSupplier = timeSupplier; + } + + + public VagrantNode get(String id) { + vacuum(); + return nodes.get(id); + } + + protected void vacuum() { + // No need to lock on lastVacuumMs - not critical if we miss/do double vacuuming. + if (timeSupplier.get() - lastVacuumMs > VACUUM_PERIOD_MS) { + TerminatedNode terminated; + while ((terminated = terminatedNodes.poll()) != null) { + nodes.remove(terminated.node.id()); + } + lastVacuumMs = timeSupplier.get(); + } + } + + public void add(VagrantNode node) { + nodes.put(node.id(), node); + } + + public void onTerminated(VagrantNode node) { + terminatedNodes.add(new TerminatedNode(node, timeSupplier)); + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantOutputRecorder.java ---------------------------------------------------------------------- diff --git a/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantOutputRecorder.java b/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantOutputRecorder.java new file mode 100644 index 0000000..32ef561 --- /dev/null +++ b/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantOutputRecorder.java @@ -0,0 +1,59 @@ +/* + * 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.jclouds.vagrant.internal; + +import vagrant.api.CommandIOListener; + +public class VagrantOutputRecorder implements CommandIOListener { + + private CommandIOListener next; + private StringBuilder output = new StringBuilder(); + private boolean isRecording; + + public VagrantOutputRecorder(CommandIOListener next) { + this.next = next; + } + + @Override + public void onInput(String input) { + if (isRecording) { + next.onInput(input); + } + } + + @Override + public void onOutput(String output) { + if (isRecording) { + next.onOutput(output); + if (output != null) { + this.output.append(output); + } + } + } + + public void record() { + isRecording = true; + } + + public String stopRecording() { + isRecording = false; + String out = output.toString(); + output.setLength(0); + return out; + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantWireLogger.java ---------------------------------------------------------------------- diff --git a/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantWireLogger.java b/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantWireLogger.java new file mode 100644 index 0000000..8e97311 --- /dev/null +++ b/vagrant/src/main/java/org/jclouds/vagrant/internal/VagrantWireLogger.java @@ -0,0 +1,65 @@ +/* + * 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.jclouds.vagrant.internal; + +import java.io.ByteArrayInputStream; + +import org.jclouds.http.internal.HttpWire; + +import com.google.common.base.Charsets; +import com.google.inject.Inject; + +import vagrant.api.CommandIOListener; + +public class VagrantWireLogger implements CommandIOListener { + private HttpWire wire; + + // Vagrant commands are sequential (non-concurrent) + private String lastPartialLine = ""; + + @Inject + VagrantWireLogger(HttpWire wire) { + this.wire = wire; + } + + @Override + public void onInput(String input) { + // Inputs are always single-line + if (input != null) { + wire.input(new ByteArrayInputStream(input.getBytes(Charsets.UTF_8))); + } + } + + @Override + public void onOutput(String output) { + if (output != null) { + int nlPos = output.indexOf('\n'); + String fullLineOutput; + if (nlPos != -1) { + fullLineOutput = lastPartialLine + output.substring(0, nlPos + 1); + lastPartialLine = output.substring(nlPos + 1); + wire.output(fullLineOutput); + } else { + lastPartialLine += output; + } + } else if (!lastPartialLine.isEmpty()) { + wire.output(lastPartialLine); + lastPartialLine = ""; + } + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/reference/VagrantConstants.java ---------------------------------------------------------------------- diff --git a/vagrant/src/main/java/org/jclouds/vagrant/reference/VagrantConstants.java b/vagrant/src/main/java/org/jclouds/vagrant/reference/VagrantConstants.java new file mode 100644 index 0000000..c008375 --- /dev/null +++ b/vagrant/src/main/java/org/jclouds/vagrant/reference/VagrantConstants.java @@ -0,0 +1,66 @@ +/* + * 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.jclouds.vagrant.reference; + +import java.io.File; + +public final class VagrantConstants { + private VagrantConstants() {} + + public static final String JCLOUDS_VAGRANT_HOME = "vagrant.home"; + public static final String JCLOUDS_VAGRANT_HOME_DEFAULT = new File(System.getProperty("user.home"), ".jclouds/vagrant").getAbsolutePath(); + public static final String VAGRANTFILE = "Vagrantfile"; + public static final String DEFAULT_USERNAME = "vagrant"; + public static final String DEFAULT_PASSWORD = "vagrant"; + public static final String USER_META_PROVIDER = "provider"; + + public static final String ENV_VAGRANT_HOME = "VAGRANT_HOME"; + public static final String ENV_VAGRANT_HOME_DEFAULT = new File(System.getProperty("user.home"), ".vagrant.d").getAbsolutePath(); + public static final String VAGRANT_BOXES_SUBFOLDER = "boxes"; + + public static final String ESCAPE_SLASH = "-VAGRANTSLASH-"; + + public static final String DELIMITER_NETWORKS_START = "================= Networks start ================="; + public static final String DELIMITER_NETWORKS_END = "================= Networks end ==================="; + public static final String DELIMITER_HOSTNAME_START = "================= Hostname start =========================="; + public static final String DELIMITER_HOSTNAME_END = "================= Hostname end ============================"; + + // Vagrantfile config + public static final String KEY_VM_GUEST = ".vm.guest"; + public static final String VM_GUEST_WINDOWS = ":windows"; + public static final String KEY_WINRM_USERNAME = ".winrm.username"; + public static final String KEY_WINRM_PASSWORD = ".winrm.password"; + public static final String KEY_WINRM_PORT = ".winrm.port"; + public static final String KEY_SSH_USERNAME = ".ssh.username"; + public static final String KEY_SSH_PASSWORD = ".ssh.password"; + public static final String KEY_SSH_PRIVATE_KEY_PATH = ".ssh.private_key_path"; + public static final String KEY_SSH_PORT = ".ssh.port"; + + public static final String MACHINES_CONFIG_SUBFOLDER = "machines"; + public static final String MACHINES_CONFIG_EXTENSION = ".yaml"; + public static final String MACHINES_AUTO_HARDWARE = "automatic"; + + // Config file keys + public static final String CONFIG_JCLOUDS_VERSION = "jcloudsVersion"; + public static final String CONFIG_BOX = "box"; + public static final String CONFIG_HARDWARE_ID = "hardwareId"; + public static final String CONFIG_OS_FAMILY = "osFamily"; + public static final String CONFIG_MEMORY = "memory"; + public static final String CONFIG_CPUS = "cpus"; + public static final String CONFIG_USERNAME = "username"; + public static final String CONFIG_PASSWORD = "password"; +}
