You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by na...@apache.org on 2017/01/25 09:58:53 UTC

[1/3] jclouds-labs git commit: JCLOUDS-1208 Implement the TemplateOptions.inboundPorts in ProfitBricks REST

Repository: jclouds-labs
Updated Branches:
  refs/heads/2.0.x 784ee0207 -> fadcd5d9f


JCLOUDS-1208 Implement the TemplateOptions.inboundPorts in ProfitBricks REST


Project: http://git-wip-us.apache.org/repos/asf/jclouds-labs/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds-labs/commit/53439188
Tree: http://git-wip-us.apache.org/repos/asf/jclouds-labs/tree/53439188
Diff: http://git-wip-us.apache.org/repos/asf/jclouds-labs/diff/53439188

Branch: refs/heads/2.0.x
Commit: 534391887a4aec64f523b2768f76f1e518f6ecda
Parents: 784ee02
Author: Ali Bazlamit <al...@hotmail.com>
Authored: Wed Jan 18 12:08:05 2017 +0200
Committer: Ignasi Barrera <na...@apache.org>
Committed: Wed Jan 25 10:57:36 2017 +0100

----------------------------------------------------------------------
 .../ProfitBricksComputeServiceAdapter.java      | 76 ++++++++++++--------
 .../ProfitBricksComputeServiceLiveTest.java     | 34 ++++++++-
 2 files changed, 78 insertions(+), 32 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/53439188/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/ProfitBricksComputeServiceAdapter.java
----------------------------------------------------------------------
diff --git a/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/ProfitBricksComputeServiceAdapter.java b/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/ProfitBricksComputeServiceAdapter.java
index be34f3f..0f15123 100644
--- a/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/ProfitBricksComputeServiceAdapter.java
+++ b/profitbricks-rest/src/main/java/org/apache/jclouds/profitbricks/rest/compute/ProfitBricksComputeServiceAdapter.java
@@ -17,33 +17,39 @@
 package org.apache.jclouds.profitbricks.rest.compute;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import com.google.common.base.Predicate;
 import static com.google.common.base.Strings.isNullOrEmpty;
+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.Iterables;
 import static com.google.common.collect.Iterables.contains;
 import static com.google.common.collect.Iterables.filter;
+import com.google.common.collect.Lists;
 import static com.google.common.util.concurrent.Futures.getUnchecked;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.inject.Inject;
 import static java.lang.String.format;
-import static org.apache.jclouds.profitbricks.rest.config.ProfitBricksComputeProperties.POLL_PREDICATE_DATACENTER;
-import static org.apache.jclouds.profitbricks.rest.config.ProfitBricksComputeProperties.POLL_PREDICATE_NIC;
-import static org.apache.jclouds.profitbricks.rest.config.ProfitBricksComputeProperties.POLL_PREDICATE_SERVER;
-import static org.jclouds.Constants.PROPERTY_USER_THREADS;
-import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING;
-import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED;
-
 import java.net.URI;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.Callable;
-
 import javax.annotation.Resource;
 import javax.inject.Named;
 import javax.inject.Singleton;
-
 import org.apache.jclouds.profitbricks.rest.ProfitBricksApi;
 import org.apache.jclouds.profitbricks.rest.compute.concurrent.ProvisioningJob;
 import org.apache.jclouds.profitbricks.rest.compute.concurrent.ProvisioningManager;
 import org.apache.jclouds.profitbricks.rest.compute.function.ProvisionableToImage;
 import org.apache.jclouds.profitbricks.rest.compute.strategy.TemplateWithDataCenter;
+import static org.apache.jclouds.profitbricks.rest.config.ProfitBricksComputeProperties.POLL_PREDICATE_DATACENTER;
+import static org.apache.jclouds.profitbricks.rest.config.ProfitBricksComputeProperties.POLL_PREDICATE_NIC;
+import static org.apache.jclouds.profitbricks.rest.config.ProfitBricksComputeProperties.POLL_PREDICATE_SERVER;
 import org.apache.jclouds.profitbricks.rest.domain.DataCenter;
+import org.apache.jclouds.profitbricks.rest.domain.FirewallRule;
 import org.apache.jclouds.profitbricks.rest.domain.Image;
 import org.apache.jclouds.profitbricks.rest.domain.Lan;
 import org.apache.jclouds.profitbricks.rest.domain.Nic;
@@ -60,7 +66,10 @@ import org.apache.jclouds.profitbricks.rest.ids.ServerRef;
 import org.apache.jclouds.profitbricks.rest.ids.VolumeRef;
 import org.apache.jclouds.profitbricks.rest.util.Passwords;
 import org.apache.jclouds.profitbricks.rest.util.Trackables;
+import static org.jclouds.Constants.PROPERTY_USER_THREADS;
 import org.jclouds.compute.ComputeServiceAdapter;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED;
 import org.jclouds.compute.domain.Hardware;
 import org.jclouds.compute.domain.HardwareBuilder;
 import org.jclouds.compute.domain.Processor;
@@ -70,23 +79,13 @@ import org.jclouds.compute.domain.internal.VolumeImpl;
 import org.jclouds.compute.options.TemplateOptions;
 import org.jclouds.compute.reference.ComputeServiceConstants;
 import org.jclouds.compute.util.ComputeServiceUtils;
+import static org.jclouds.compute.util.ComputeServiceUtils.getPortRangesFromList;
 import org.jclouds.domain.Location;
 import org.jclouds.domain.LocationScope;
 import org.jclouds.domain.LoginCredentials;
 import org.jclouds.logging.Logger;
 import org.jclouds.rest.ResourceNotFoundException;
 
-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.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.inject.Inject;
-
 @Singleton
 public class ProfitBricksComputeServiceAdapter implements ComputeServiceAdapter<ServerInDataCenter, Hardware, Provisionable, Location> {
 
@@ -149,6 +148,7 @@ public class ProfitBricksComputeServiceAdapter implements ComputeServiceAdapter<
       final String loginUser = isNullOrEmpty(options.getLoginUser()) ? "root" : options.getLoginUser();
       final String password = options.hasLoginPassword() ? options.getLoginPassword() : Passwords.generate();
       final org.jclouds.compute.domain.Image image = template.getImage();
+      final int[] inboundPorts = template.getOptions().getInboundPorts();
 
       // provision all volumes based on hardware
       List<? extends Volume> volumes = hardware.getVolumes();
@@ -175,12 +175,12 @@ public class ProfitBricksComputeServiceAdapter implements ComputeServiceAdapter<
                     type(VolumeType.HDD);
 
             org.apache.jclouds.profitbricks.rest.domain.Volume vol = (org.apache.jclouds.profitbricks.rest.domain.Volume) provisioningManager
-                  .provision(jobFactory.create(dataCenterId, new Supplier<Object>() {
-               @Override
-               public Object get() {
-                  return api.volumeApi().createVolume(request.build());
-               }
-            }));
+                    .provision(jobFactory.create(dataCenterId, new Supplier<Object>() {
+                       @Override
+                       public Object get() {
+                          return api.volumeApi().createVolume(request.build());
+                       }
+                    }));
 
             volumeIds.add(vol.id());
             logger.trace(">> provisioning complete for volume. returned id='%s'", vol.id());
@@ -281,15 +281,33 @@ public class ProfitBricksComputeServiceAdapter implements ComputeServiceAdapter<
               .name("jclouds" + name)
               .dhcp(Boolean.TRUE)
               .lan(lanId)
-              .firewallActive(Boolean.FALSE)
+              .firewallActive(inboundPorts.length > 0)
               .serverId(server.id()).
               build());
-      
+
       trackables.waitUntilRequestCompleted(nic);
       waitNICUntilAvailable.apply(NicRef.create(dataCenterId, server.id(), nic.id()));
       waitDcUntilAvailable.apply(dataCenterId);
       waitServerUntilAvailable.apply(ServerRef.create(dataCenterId, server.id()));
 
+      Map<Integer, Integer> portsRange = getPortRangesFromList(inboundPorts);
+
+      for (Map.Entry<Integer, Integer> range : portsRange.entrySet()) {
+         FirewallRule rule = api.firewallApi().create(
+                 FirewallRule.Request.creatingBuilder()
+                 .dataCenterId(dataCenterId)
+                 .serverId(server.id())
+                 .nicId(nic.id())
+                 .name(server.properties().name() + " jclouds-firewall")
+                 .protocol(FirewallRule.Protocol.TCP)
+                 .portRangeStart(range.getKey())
+                 .portRangeEnd(range.getValue())
+                 .build()
+         );
+         trackables.waitUntilRequestCompleted(rule);
+
+      }
+
       //connect the rest of volumes to server;delete if fails
       final int volumeCount = volumeIds.size();
       for (int j = 1; j < volumeCount; j++) { // skip first; already connected
@@ -400,7 +418,7 @@ public class ProfitBricksComputeServiceAdapter implements ComputeServiceAdapter<
       ImmutableList.Builder<Provisionable> provisionables = ImmutableList.builder();
       provisionables.addAll(getUnchecked(images));
       provisionables.addAll(getUnchecked(snapshots));
-      
+
       return provisionables.build();
    }
 

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/53439188/profitbricks-rest/src/test/java/org/apache/jclouds/profitbricks/rest/compute/ProfitBricksComputeServiceLiveTest.java
----------------------------------------------------------------------
diff --git a/profitbricks-rest/src/test/java/org/apache/jclouds/profitbricks/rest/compute/ProfitBricksComputeServiceLiveTest.java b/profitbricks-rest/src/test/java/org/apache/jclouds/profitbricks/rest/compute/ProfitBricksComputeServiceLiveTest.java
index a3a15b3..3f89fe5 100644
--- a/profitbricks-rest/src/test/java/org/apache/jclouds/profitbricks/rest/compute/ProfitBricksComputeServiceLiveTest.java
+++ b/profitbricks-rest/src/test/java/org/apache/jclouds/profitbricks/rest/compute/ProfitBricksComputeServiceLiveTest.java
@@ -18,24 +18,34 @@ package org.apache.jclouds.profitbricks.rest.compute;
 
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
 import static com.google.common.collect.Iterables.getOnlyElement;
 import com.google.inject.Module;
+import org.apache.jclouds.profitbricks.rest.ProfitBricksApi;
+import org.apache.jclouds.profitbricks.rest.config.ProfitBricksRateLimitModule;
+import org.apache.jclouds.profitbricks.rest.domain.FirewallRule;
+import org.apache.jclouds.profitbricks.rest.domain.Server;
+import org.apache.jclouds.profitbricks.rest.domain.options.DepthOptions;
+import org.apache.jclouds.profitbricks.rest.domain.zonescoped.DataCenterAndId;
 import static org.assertj.core.api.Assertions.assertThat;
+import org.jclouds.compute.RunNodesException;
 import org.jclouds.compute.domain.ExecResponse;
 import org.jclouds.compute.domain.NodeMetadata;
 import org.jclouds.compute.domain.Template;
 import org.jclouds.compute.internal.BaseComputeServiceLiveTest;
+import org.jclouds.compute.predicates.NodePredicates;
 import static org.jclouds.compute.predicates.NodePredicates.inGroup;
-
-import org.apache.jclouds.profitbricks.rest.config.ProfitBricksRateLimitModule;
 import org.jclouds.logging.config.LoggingModule;
 import org.jclouds.logging.slf4j.config.SLF4JLoggingModule;
 import org.jclouds.sshj.config.SshjSshClientModule;
+import org.testng.Assert;
 import org.testng.annotations.Test;
 
 @Test(groups = "live", singleThreaded = true, testName = "ProfitBricksComputeServiceLiveTest")
 public class ProfitBricksComputeServiceLiveTest extends BaseComputeServiceLiveTest {
 
+   static ProfitBricksApi pbApi;
+
    public ProfitBricksComputeServiceLiveTest() {
       provider = "profitbricks-rest";
    }
@@ -49,7 +59,7 @@ public class ProfitBricksComputeServiceLiveTest extends BaseComputeServiceLiveTe
    protected LoggingModule getLoggingModule() {
       return new SLF4JLoggingModule();
    }
-   
+
    @Override
    protected Iterable<Module> setupModules() {
       ImmutableSet.Builder<Module> modules = ImmutableSet.builder();
@@ -84,6 +94,23 @@ public class ProfitBricksComputeServiceLiveTest extends BaseComputeServiceLiveTe
    }
 
    @Override
+   protected void createAndRunAServiceInGroup(String group) throws RunNodesException {
+      super.createAndRunAServiceInGroup(group);
+      pbApi = client.getContext().unwrapApi(ProfitBricksApi.class);
+
+      int matches = 0;
+      NodeMetadata node = Iterables.getOnlyElement(client.listNodesDetailsMatching(NodePredicates.inGroup(group)));
+      DataCenterAndId datacenterAndId = DataCenterAndId.fromSlashEncoded(node.getId());
+      Server server = pbApi.serverApi().getServer(datacenterAndId.getDataCenter(), datacenterAndId.getId(), new DepthOptions().depth(5));
+      for (FirewallRule rule : server.entities().nics().items().get(0).entities().firewallrules().items()) {
+         if (rule.properties().portRangeStart() == 22 || rule.properties().portRangeStart() == 8080) {
+            matches++;
+         }
+      }
+      Assert.assertEquals(2, matches);
+   }
+
+   @Override
    @Test
    public void testCreateNodeWithCustomHardware() throws Exception {
       Template template = buildTemplate(templateBuilder()
@@ -113,4 +140,5 @@ public class ProfitBricksComputeServiceLiveTest extends BaseComputeServiceLiveTe
          client.destroyNodesMatching(inGroup(group + "custom"));
       }
    }
+
 }


[3/3] jclouds-labs git commit: Add Vagrant provider

Posted by na...@apache.org.
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 <sv...@neykov.name>
Authored: Thu Apr 2 22:35:47 2015 +0300
Committer: Ignasi Barrera <na...@apache.org>
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";
+}


[2/3] jclouds-labs git commit: Add Vagrant provider

Posted by na...@apache.org.
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/strategy/VagrantDefaultImageCredentials.java
----------------------------------------------------------------------
diff --git a/vagrant/src/main/java/org/jclouds/vagrant/strategy/VagrantDefaultImageCredentials.java b/vagrant/src/main/java/org/jclouds/vagrant/strategy/VagrantDefaultImageCredentials.java
new file mode 100644
index 0000000..97667cf
--- /dev/null
+++ b/vagrant/src/main/java/org/jclouds/vagrant/strategy/VagrantDefaultImageCredentials.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.strategy;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.OsFamily;
+import org.jclouds.compute.strategy.PopulateDefaultLoginCredentialsForImageStrategy;
+import org.jclouds.domain.Credentials;
+import org.jclouds.domain.LoginCredentials;
+import org.jclouds.domain.LoginCredentials.Builder;
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.logging.Logger;
+import org.jclouds.vagrant.internal.BoxConfig;
+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 com.google.inject.Singleton;
+
+@Singleton
+public class VagrantDefaultImageCredentials implements PopulateDefaultLoginCredentialsForImageStrategy {
+
+   @Resource
+   protected Logger logger = Logger.NULL;
+
+   protected final LoginCredentials creds;
+   protected final Map<String, Credentials> credentialStore;
+   protected final BoxConfig.Factory boxConfigFactory;
+
+   @Inject
+   VagrantDefaultImageCredentials(
+         @Nullable @Named("image") LoginCredentials creds,
+         Map<String, Credentials> credentialStore,
+         BoxConfig.Factory boxConfigFactory) {
+      this.creds = creds;
+      this.credentialStore = checkNotNull(credentialStore, "credentialStore");
+      this.boxConfigFactory = checkNotNull(boxConfigFactory, "boxConfigFactory");
+   }
+
+   @Override
+   public LoginCredentials apply(Object resourceToAuthenticate) {
+      checkState(resourceToAuthenticate instanceof Image, "this is only valid for images, not %s",
+            resourceToAuthenticate.getClass().getSimpleName());
+      if (creds != null)
+         return creds;
+      Image image = Image.class.cast(resourceToAuthenticate);
+      if (credentialStore.containsKey("image#" + image.getId())) {
+         return LoginCredentials.fromCredentials(credentialStore.get("image#" + image.getId()));
+      // Skipping osFamilyToCredentials - not applicable to vagrant world
+      } else if (image.getOperatingSystem().getFamily() == OsFamily.WINDOWS) {
+         return parseWinRmBoxCredentials(image);
+      } else {
+         return parseSshBoxCredentials(image);
+      }
+   }
+
+   private LoginCredentials parseWinRmBoxCredentials(Image image) {
+      BoxConfig parser = boxConfigFactory.newInstance(image);
+      String username = parser.getStringKey(VagrantConstants.KEY_WINRM_USERNAME).or(VagrantConstants.DEFAULT_USERNAME);
+      String password = parser.getStringKey(VagrantConstants.KEY_WINRM_PASSWORD).or(VagrantConstants.DEFAULT_PASSWORD);
+      return LoginCredentials.builder()
+            .user(username)
+            .password(password)
+            .noPrivateKey()
+            .build();
+   }
+
+   private LoginCredentials parseSshBoxCredentials(Image image) {
+      BoxConfig parser = boxConfigFactory.newInstance(image);
+      String username = parser.getStringKey(VagrantConstants.KEY_SSH_USERNAME).or(VagrantConstants.DEFAULT_USERNAME);
+      Builder credBuilder = LoginCredentials.builder().user(username);
+      Optional<String> password = parser.getStringKey(VagrantConstants.KEY_SSH_PASSWORD);
+      if (password.isPresent()) {
+         credBuilder.password(password.get());
+      }
+      Optional<String> privateKeyPath = parser.getStringKey(VagrantConstants.KEY_SSH_PRIVATE_KEY_PATH);
+      if (privateKeyPath.isPresent()) {
+         File privateKey = new File(parser.getFolder(), privateKeyPath.get());
+         if (privateKey.exists()) {
+            try {
+               credBuilder.privateKey(Files.toString(privateKey, Charsets.UTF_8));
+            } catch (IOException e) {
+               throw new IllegalStateException("Failure reading private key file " +
+                     privateKey.getAbsolutePath() + " for box " + parser.getFolder().getAbsolutePath());
+            }
+         } else {
+            logger.warn("Private key " + privateKeyPath.get() + " for box " +
+                  parser.getFolder().getAbsolutePath() + " not found at " + privateKey.getAbsolutePath() + ". Ignoring.");
+         }
+      }
+      return credBuilder.build();
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/suppliers/VagrantHardwareSupplier.java
----------------------------------------------------------------------
diff --git a/vagrant/src/main/java/org/jclouds/vagrant/suppliers/VagrantHardwareSupplier.java b/vagrant/src/main/java/org/jclouds/vagrant/suppliers/VagrantHardwareSupplier.java
new file mode 100644
index 0000000..f048b48
--- /dev/null
+++ b/vagrant/src/main/java/org/jclouds/vagrant/suppliers/VagrantHardwareSupplier.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.vagrant.suppliers;
+
+import java.util.Map;
+
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.HardwareBuilder;
+import org.jclouds.compute.domain.Processor;
+import org.jclouds.compute.domain.Volume.Type;
+import org.jclouds.compute.domain.VolumeBuilder;
+
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableMap;
+
+public class VagrantHardwareSupplier implements Supplier<Map<String, Hardware>> {
+   private static final Map<String, Hardware> HARDWARE = ImmutableMap.of(
+         "micro", hardware("micro", 512, 1),
+         "small", hardware("small", 1024, 1),
+         "medium", hardware("medium", 2048, 2),
+         "large", hardware("large", 4096, 2),
+         "xlarge", hardware("xlarge", 8192, 4));
+
+   private static Hardware hardware(String name, int ram, int cores) {
+      return new HardwareBuilder()
+            .ids(name)
+            .hypervisor("VirtualBox")
+            .name(name)
+            .processor(new Processor(cores, 1))
+            .ram(ram)
+            .volume(new VolumeBuilder().bootDevice(true).durable(true).type(Type.LOCAL).build())
+            .build();
+   }
+
+   @Override
+   public Map<String, Hardware> get() {
+      return HARDWARE;
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/java/org/jclouds/vagrant/util/VagrantUtils.java
----------------------------------------------------------------------
diff --git a/vagrant/src/main/java/org/jclouds/vagrant/util/VagrantUtils.java b/vagrant/src/main/java/org/jclouds/vagrant/util/VagrantUtils.java
new file mode 100644
index 0000000..68c6249
--- /dev/null
+++ b/vagrant/src/main/java/org/jclouds/vagrant/util/VagrantUtils.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.jclouds.vagrant.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.jclouds.util.Closeables2;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.ByteStreams;
+
+public class VagrantUtils {
+   public static void deleteFolder(File path) {
+      if (path.isDirectory()) {
+         for (File sub : path.listFiles()) {
+            deleteFolder(sub);
+         }
+      }
+      if (!path.delete()) {
+         throw new IllegalStateException("Can't delete " + path.getAbsolutePath());
+      }
+   }
+
+   public static void write(File file, String value) throws IOException {
+      write(file, new ByteArrayInputStream(value.getBytes(Charsets.UTF_8)));
+   }
+
+   public static void write(File file, InputStream in) throws IOException {
+      OutputStream out = new FileOutputStream(file);
+      try {
+         ByteStreams.copy(in, out);
+      } finally {
+         Closeables2.closeQuietly(out);
+         Closeables2.closeQuietly(in);
+      }
+   }
+
+   public static void deleteFiles(File path, String filePattern) {
+      for (File f : path.listFiles()) {
+         if (f.getName().startsWith(filePattern)) {
+            if (!f.delete()) {
+               throw new IllegalStateException("Failed deleting machine file " + f.getAbsolutePath());
+            }
+         }
+      }
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/main/resources/Vagrantfile
----------------------------------------------------------------------
diff --git a/vagrant/src/main/resources/Vagrantfile b/vagrant/src/main/resources/Vagrantfile
new file mode 100644
index 0000000..9912ff1
--- /dev/null
+++ b/vagrant/src/main/resources/Vagrantfile
@@ -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.
+
+require 'yaml'
+
+# Because of linked_clone. 1.9+ recommended for Ubuntu Xenial
+Vagrant.require_version ">= 1.8"
+
+Vagrant.configure(2) do |config|
+  Dir.glob('machines/*.yaml') do |machine_file|
+    machine_config = YAML.load_file(machine_file)
+    name = File.basename(machine_file, ".yaml")
+    config.vm.define name do |config|
+      config.vm.box = machine_config["box"]
+      config.vm.box_check_update = false
+      config.vm.network "private_network", type: "dhcp"
+      config.vm.synced_folder '.', '/vagrant', disabled: true
+      config.ssh.username = machine_config["username"] if machine_config.key?("username") 
+      config.ssh.password = machine_config["password"] if machine_config.key?("password")
+      config.ssh.private_key_path = machine_config["private_key_path"] if machine_config.key?("private_key_path")
+
+      isWindows = (machine_config["osFamily"] == "windows");
+      if isWindows
+        # That's a Powershell script.
+        # Go through a temporary file, otherwise getting the following error:
+
+        # out-lineoutput : The OS handle's position is not what FileStream expected. Do n
+        # ot use a handle simultaneously in one FileStream and in Win32 code or another F
+        # ileStream. This may cause data loss.
+        #     + CategoryInfo          : NotSpecified: (:) [out-lineoutput], IOException
+        #     + FullyQualifiedErrorId : System.IO.IOException,Microsoft.PowerShell.Comma 
+        #     nds.OutLineOutputCommand
+
+        config.vm.provision "shell", inline: <<-EOF
+          $tmp = [System.IO.Path]::GetTempFileName()
+          echo "================= Networks start =================" > $tmp
+          ipconfig | find "IPv4 Address" >> $tmp 2>&1
+          echo "================= Networks end ===================" >> $tmp
+          echo "================= Hostname start ==========================" >> $tmp
+          hostname >> $tmp 2>&1
+          echo "================= Hostname end ============================" >> $tmp
+          type $tmp
+        EOF
+      else
+        config.vm.provision "shell", inline: <<-EOF
+          echo "================= Networks start ================="
+          ip address show | grep 'scope global' 2>&1
+          echo "================= Networks end ==================="
+          echo "================= Hostname start =========================="
+          hostname 2>&1
+          echo "================= Hostname end ============================"
+        EOF
+      end
+
+      config.vm.provider "virtualbox" do |v|
+        v.gui = false
+        v.memory = machine_config["memory"] if machine_config.key?("memory")
+        v.cpus = machine_config["cpus"] if machine_config.key?("cpus")
+        v.linked_clone = true
+        # Windows needs additional drivers for virtio
+        if !isWindows
+          v.customize ["modifyvm", :id, "--nictype1", "virtio"]
+          v.customize ["modifyvm", :id, "--nictype2", "virtio"]
+        end
+      end
+    end
+  end
+end
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/test/java/org/jclouds/vagrant/compute/BoxConfigLiveTest.java
----------------------------------------------------------------------
diff --git a/vagrant/src/test/java/org/jclouds/vagrant/compute/BoxConfigLiveTest.java b/vagrant/src/test/java/org/jclouds/vagrant/compute/BoxConfigLiveTest.java
new file mode 100644
index 0000000..3475f43
--- /dev/null
+++ b/vagrant/src/test/java/org/jclouds/vagrant/compute/BoxConfigLiveTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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 org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.internal.BaseComputeServiceContextLiveTest;
+import org.jclouds.vagrant.internal.BoxConfig;
+import org.jclouds.vagrant.reference.VagrantConstants;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Optional;
+
+@Test(groups = "live", testName = "BoxConfigLiveTest")
+public class BoxConfigLiveTest extends BaseComputeServiceContextLiveTest {
+
+   public BoxConfigLiveTest() {
+      provider = "vagrant";
+   }
+
+   @Test
+   public void testDefaultCredentials() {
+      Image image = view.getComputeService().getImage("ubuntu/trusty64");
+
+      BoxConfig.Factory boxConfigFactory = new BoxConfig.Factory();
+      BoxConfig boxConfig = boxConfigFactory.newInstance(image);
+
+      assertFalse(boxConfig.getStringKey(VagrantConstants.CONFIG_USERNAME).isPresent());
+      assertFalse(boxConfig.getStringKey(VagrantConstants.CONFIG_PASSWORD).isPresent());
+   }
+
+   @Test
+   public void testCustomUsernameAndPassword() {
+      Image image = view.getComputeService().getImage("ubuntu/xenial64");
+
+      BoxConfig.Factory boxConfigFactory = new BoxConfig.Factory();
+      BoxConfig boxConfig = boxConfigFactory.newInstance(image);
+
+      assertEquals(boxConfig.getStringKey(VagrantConstants.CONFIG_USERNAME), Optional.of("ubuntu"));
+      // Password changes on each box update
+      assertTrue(boxConfig.getStringKey(VagrantConstants.CONFIG_PASSWORD).isPresent());
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/test/java/org/jclouds/vagrant/compute/VagrantComputeServiceAdapterLiveTest.java
----------------------------------------------------------------------
diff --git a/vagrant/src/test/java/org/jclouds/vagrant/compute/VagrantComputeServiceAdapterLiveTest.java b/vagrant/src/test/java/org/jclouds/vagrant/compute/VagrantComputeServiceAdapterLiveTest.java
new file mode 100644
index 0000000..58580ca
--- /dev/null
+++ b/vagrant/src/test/java/org/jclouds/vagrant/compute/VagrantComputeServiceAdapterLiveTest.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.vagrant.compute;
+
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.OsFamily;
+import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.internal.BaseComputeServiceLiveTest;
+import org.jclouds.sshj.config.SshjSshClientModule;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.Module;
+
+public class VagrantComputeServiceAdapterLiveTest extends BaseComputeServiceLiveTest {
+
+   public VagrantComputeServiceAdapterLiveTest() {
+      provider = "vagrant";
+   }
+
+   @Override
+   protected Module getSshModule() {
+      return new SshjSshClientModule();
+   }
+
+   @Override
+   @Test(enabled = true)
+   public void testCorrectAuthException() throws Exception {
+      // Vagrant doesn't use credential info
+   }
+
+   @Override
+   protected void checkTagsInNodeEquals(NodeMetadata node, ImmutableSet<String> tags) {
+      // Vagrant doesn't support tags
+      // TODO Could store it in the json
+   }
+
+   @Override
+   protected void checkUserMetadataContains(NodeMetadata node, ImmutableMap<String, String> userMetadata) {
+      // Vagrant doesn't support user metadata
+      // TODO Could store it in the json
+   }
+
+   @Override
+   @Test(enabled = true, dependsOnMethods = "testGet")
+   public void testOptionToNotBlock() throws Exception {
+       // LoginCredentials are available only after the machine starts,
+       // so can't return earlier.
+   }
+
+   @Override
+   @Test(enabled = true, dependsOnMethods = { "testCompareSizes" })
+   public void testAScriptExecutionAfterBootWithBasicTemplate() throws Exception {
+      // Fails on CentOS 7. Can't ssh back with user foo because SELinux not configured correctly.
+      // "foo" is created out of the /home folder, /over/ridden is not white listed with the correct context.
+      // Steps needed to configure SELinux before creating the user:
+      //
+      // semanage fcontext -a -e /home /over/ridden
+      // mkdir /over/ridden
+      // restorecon /over/ridden
+      // useradd -d /over/ridden/foo foo
+      //
+      // semanage is not available on a default install - needs "yum install policycoreutils-python"
+
+      Template template = buildTemplate(templateBuilder());
+      if (template.getImage().getOperatingSystem().getFamily() != OsFamily.CENTOS) {
+         super.testAScriptExecutionAfterBootWithBasicTemplate();
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/test/java/org/jclouds/vagrant/compute/VagrantTemplateBuilderLiveTest.java
----------------------------------------------------------------------
diff --git a/vagrant/src/test/java/org/jclouds/vagrant/compute/VagrantTemplateBuilderLiveTest.java b/vagrant/src/test/java/org/jclouds/vagrant/compute/VagrantTemplateBuilderLiveTest.java
new file mode 100644
index 0000000..dcb8040
--- /dev/null
+++ b/vagrant/src/test/java/org/jclouds/vagrant/compute/VagrantTemplateBuilderLiveTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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 org.jclouds.compute.domain.OsFamily.UBUNTU;
+import static org.jclouds.compute.domain.OsFamily.CENTOS;
+import static org.jclouds.compute.util.ComputeServiceUtils.getCores;
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.Set;
+
+import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.internal.BaseTemplateBuilderLiveTest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+
+@Test(groups = "live", testName = "VagrantTemplateBuilderLiveTest")
+public class VagrantTemplateBuilderLiveTest extends BaseTemplateBuilderLiveTest {
+
+   public VagrantTemplateBuilderLiveTest() {
+      provider = "vagrant";
+   }
+
+   @Override
+   protected Set<String> getIso3166Codes() {
+      return ImmutableSet.of();
+   }
+
+   @Test
+   @Override
+   public void testDefaultTemplateBuilder() throws IOException {
+      Template defaultTemplate = view.getComputeService().templateBuilder().build();
+      String imageId = defaultTemplate.getImage().getId();
+      if (imageId.startsWith("ubuntu")) {
+         assertEquals(defaultTemplate.getImage().getOperatingSystem().getFamily(), UBUNTU);
+      } else if (imageId.startsWith("centos")) {
+         assertEquals(defaultTemplate.getImage().getOperatingSystem().getFamily(), CENTOS);
+      }
+      assertEquals(defaultTemplate.getImage().getOperatingSystem().is64Bit(), true);
+      assertEquals(defaultTemplate.getHardware().getName(), "micro");
+      assertEquals(getCores(defaultTemplate.getHardware()), 1.0d);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/test/java/org/jclouds/vagrant/compute/WindowsLiveTest.java
----------------------------------------------------------------------
diff --git a/vagrant/src/test/java/org/jclouds/vagrant/compute/WindowsLiveTest.java b/vagrant/src/test/java/org/jclouds/vagrant/compute/WindowsLiveTest.java
new file mode 100644
index 0000000..a8887e7
--- /dev/null
+++ b/vagrant/src/test/java/org/jclouds/vagrant/compute/WindowsLiveTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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 org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Set;
+
+import org.jclouds.compute.ComputeService;
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.OperatingSystem;
+import org.jclouds.compute.domain.OsFamily;
+import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.domain.TemplateBuilder;
+import org.jclouds.compute.internal.BaseComputeServiceContextLiveTest;
+import org.jclouds.domain.LoginCredentials;
+import org.jclouds.vagrant.internal.BoxConfig;
+import org.jclouds.vagrant.reference.VagrantConstants;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.Iterables;
+
+/**
+ * Building the image:
+ *   $ git clone https://github.com/boxcutter/windows.git boxcutter-windows
+ *   $ cd boxcutter-windows
+ *   $ make virtualbox/eval-win7x86-enterprise
+ *   $ vagrant box add boxcutter/eval-win7x86-enterprise box/virtualbox/eval-win7x86-enterprise-nocm-1.0.4.box
+ */
+@Test(groups = "live", singleThreaded = true, enabled = true, testName = "WindowsLiveTest")
+public class WindowsLiveTest extends BaseComputeServiceContextLiveTest {
+
+   protected ComputeService client;
+
+   public WindowsLiveTest() {
+      provider = "vagrant";
+   }
+
+   @Override
+   protected void initializeContext() {
+      super.initializeContext();
+      client = view.getComputeService();
+   }
+
+   protected TemplateBuilder templateBuilder() {
+      TemplateBuilder templateBuilder = client.templateBuilder();
+      if (templateBuilderSpec != null) {
+         templateBuilder = templateBuilder.from(templateBuilderSpec);
+      }
+      templateBuilder.imageId(getImageId());
+      return templateBuilder;
+   }
+
+   private String getImageId() {
+      return "boxcutter/eval-win7x86-enterprise";
+   }
+
+   protected Template buildTemplate(TemplateBuilder templateBuilder) {
+      return templateBuilder.build();
+   }
+
+   @Test
+   public void testGet() throws Exception {
+      Set<? extends NodeMetadata> nodes = client.createNodesInGroup("vagrant-win", 1, buildTemplate(templateBuilder()));
+      NodeMetadata node = Iterables.getOnlyElement(nodes);
+      OperatingSystem os = node.getOperatingSystem();
+      LoginCredentials creds = node.getCredentials();
+      assertEquals(os.getFamily(), OsFamily.WINDOWS);
+      assertEquals(creds.getUser(), "vagrant");
+      assertTrue(creds.getOptionalPassword().isPresent(), "password expected");
+      assertEquals(creds.getOptionalPassword().get(), "vagrant");
+      assertFalse(creds.getOptionalPrivateKey().isPresent(), "no private key expected for windows");
+      assertEquals(node.getLoginPort(), 5985);
+      client.destroyNode(node.getId());
+   }
+
+
+   @Test
+   public void testBoxConfig() {
+      Image image = view.getComputeService().getImage(getImageId());
+
+      BoxConfig.Factory boxConfigFactory = new BoxConfig.Factory();
+      BoxConfig boxConfig = boxConfigFactory.newInstance(image);
+
+      assertEquals(boxConfig.getStringKey(".vm.communicator"), Optional.of("winrm"));
+      assertEquals(boxConfig.getKey(VagrantConstants.KEY_VM_GUEST), Optional.of(VagrantConstants.VM_GUEST_WINDOWS));
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/test/java/org/jclouds/vagrant/functions/BoxToImageTest.java
----------------------------------------------------------------------
diff --git a/vagrant/src/test/java/org/jclouds/vagrant/functions/BoxToImageTest.java b/vagrant/src/test/java/org/jclouds/vagrant/functions/BoxToImageTest.java
new file mode 100644
index 0000000..ac6307c
--- /dev/null
+++ b/vagrant/src/test/java/org/jclouds/vagrant/functions/BoxToImageTest.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.jclouds.vagrant.functions;
+
+import static org.testng.Assert.assertEquals;
+
+import org.easymock.EasyMock;
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.OsFamily;
+import org.jclouds.compute.domain.Image.Status;
+import org.jclouds.vagrant.internal.BoxConfig;
+import org.jclouds.vagrant.reference.VagrantConstants;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
+
+import vagrant.api.domain.Box;
+
+public class BoxToImageTest {
+   
+   @DataProvider(name = "boxedProvider")
+   public Object[][] boxesProvider() {
+      return new Object[][] {
+         {new Box("centos/7", "20161226", "virtualbox"), null, OsFamily.CENTOS},
+         {new Box("ubuntu/xenial64", "20161226", "virtualbox"), null, OsFamily.UBUNTU},
+         {new Box("windows-eval", "20161226", "virtualbox"), null, OsFamily.WINDOWS},
+         {new Box("some-random-name", "20161226", "virtualbox"), Optional.of(VagrantConstants.VM_GUEST_WINDOWS), OsFamily.WINDOWS},
+         {new Box("some-random-name", "20161226", "virtualbox"), Optional.absent(), OsFamily.UNRECOGNIZED},
+      };
+   }
+
+   @Test(dataProvider = "boxedProvider")
+   public void testBoxToImage(Box box, Optional<String> guestType, OsFamily osFamilyExpected) {
+      BoxConfig boxConfig = EasyMock.createMock(BoxConfig.class);
+      if (guestType != null) {
+         EasyMock.expect(boxConfig.getKey(VagrantConstants.KEY_VM_GUEST)).andReturn(guestType);
+      }
+
+      BoxConfig.Factory boxConfigFactory = EasyMock.createMock(BoxConfig.Factory.class);
+      EasyMock.expect(boxConfigFactory.newInstance(EasyMock.<Box>anyObject())).andReturn(boxConfig);
+
+      EasyMock.replay(boxConfigFactory, boxConfig);
+
+      BoxToImage boxToImage = new BoxToImage(boxConfigFactory);
+      Image image = boxToImage.apply(box);
+
+      assertEquals(image.getId(), box.getName());
+      assertEquals(image.getProviderId(), box.getName());
+      assertEquals(image.getName(), box.getName());
+      assertEquals(image.getVersion(), box.getVersion());
+      assertEquals(image.getOperatingSystem().getFamily(), osFamilyExpected);
+      assertEquals(image.getOperatingSystem().getName(), box.getName());
+      assertEquals(image.getOperatingSystem().getVersion(), box.getVersion());
+      assertEquals(image.getOperatingSystem().getDescription(), box.getName());
+      assertEquals(image.getStatus(), Status.AVAILABLE);
+      assertEquals(image.getUserMetadata(), ImmutableMap.of(VagrantConstants.USER_META_PROVIDER, box.getProvider()));
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/test/java/org/jclouds/vagrant/functions/MachineToNodeMetadataTest.java
----------------------------------------------------------------------
diff --git a/vagrant/src/test/java/org/jclouds/vagrant/functions/MachineToNodeMetadataTest.java b/vagrant/src/test/java/org/jclouds/vagrant/functions/MachineToNodeMetadataTest.java
new file mode 100644
index 0000000..7f5f5f2
--- /dev/null
+++ b/vagrant/src/test/java/org/jclouds/vagrant/functions/MachineToNodeMetadataTest.java
@@ -0,0 +1,295 @@
+/*
+ * 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 org.testng.Assert.assertEquals;
+
+import java.io.File;
+import java.util.Map;
+import java.util.Set;
+
+import org.easymock.EasyMock;
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.HardwareBuilder;
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.ImageBuilder;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.NodeMetadata.Status;
+import org.jclouds.compute.domain.NodeMetadataBuilder;
+import org.jclouds.compute.domain.OperatingSystem;
+import org.jclouds.compute.domain.OsFamily;
+import org.jclouds.compute.domain.Processor;
+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 org.testng.annotations.Test;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+import vagrant.api.domain.Box;
+
+public class MachineToNodeMetadataTest {
+   private abstract static class MachineToNodeMetadataFixture {
+      @Test
+      public void doTest() {
+         OperatingSystem os = new OperatingSystem(getOsFamily(), "Jclouds OS", "10", "x64", "Jclouds Test Image", true);
+         Image image = new ImageBuilder()
+               .ids("jclouds/box")
+               .operatingSystem(os)
+               .status(org.jclouds.compute.domain.Image.Status.AVAILABLE)
+               .build();
+
+         ImmutableList<String> networks = ImmutableList.of("172.28.128.3");
+         VagrantNode node = VagrantNode.builder()
+               .setPath(new File("/path/to/machine"))
+               .setId("vagrant/node")
+               .setGroup("vagrant")
+               .setName("node")
+               .setImage(image)
+               .setNetworks(networks)
+               .setHostname("vagrant-node")
+               .build();
+
+         node.setMachineState(Status.RUNNING);
+
+         Location location = EasyMock.createMock(Location.class);
+
+         BoxConfig boxConfig = EasyMock.createMock(BoxConfig.class);
+         expectBoxConfig(boxConfig);
+
+         BoxConfig.Factory boxConfigFactory = EasyMock.createMock(BoxConfig.Factory.class);
+         EasyMock.expect(boxConfigFactory.newInstance((Image)EasyMock.<Box>anyObject())).andReturn(boxConfig);
+
+         MachineConfig machineConfig = EasyMock.createMock(MachineConfig.class);
+         EasyMock.expect(machineConfig.load()).andReturn(getMachineConfig());
+
+         MachineConfig.Factory machineConfigFactory = EasyMock.createMock(MachineConfig.Factory.class);
+         EasyMock.expect(machineConfigFactory.newInstance(node)).andReturn(machineConfig);
+
+         Hardware hardware = new HardwareBuilder().ids(getHardwareId()).ram(100).processor(new Processor(1.0, 1)).build();
+         Supplier<? extends Map<String, Hardware>> hardwareSupplier = Suppliers.ofInstance(ImmutableMap.of(getHardwareId(), hardware));
+
+         EasyMock.replay(location, boxConfig, boxConfigFactory,
+               machineConfig, machineConfigFactory);
+
+         @SuppressWarnings({ "unchecked", "rawtypes" })
+         Supplier<Set<? extends Location>> locations = (Supplier<Set<? extends Location>>)(Supplier)Suppliers.ofInstance(ImmutableSet.of(location));
+
+         MachineToNodeMetadata machineToNodeMetadata = new MachineToNodeMetadata(
+               locations,
+               boxConfigFactory,
+               machineConfigFactory,
+               hardwareSupplier);
+
+         NodeMetadata nodeMetadataActual = machineToNodeMetadata.apply(node);
+
+         NodeMetadataBuilder nodeMetadataBuilder = new NodeMetadataBuilder()
+               .ids("vagrant/node")
+               .name("node")
+               .location(location)
+               .group("vagrant")
+               .imageId("jclouds/box")
+               .operatingSystem(os)
+               .status(Status.RUNNING)
+               .hostname("vagrant-node")
+               .privateAddresses(networks)
+               .hardware(hardware);
+         customizeBuilder(nodeMetadataBuilder);
+         NodeMetadata nodeMetadataExpected = nodeMetadataBuilder
+               .build();
+
+         assertEquals(nodeMetadataActual.toString(), nodeMetadataExpected.toString());
+      }
+
+      protected Map<String, Object> getMachineConfig() {
+         return ImmutableMap.<String, Object>of(VagrantConstants.CONFIG_HARDWARE_ID, getHardwareId());
+      }
+
+      protected abstract void customizeBuilder(NodeMetadataBuilder nodeMetadataBuilder);
+      protected abstract String getHardwareId();
+      protected abstract OsFamily getOsFamily();
+      protected abstract void expectBoxConfig(BoxConfig boxConfig);
+   }
+
+   @Test
+   public void testMiniLinux() {
+      class MachineToNodeMetadataLinuxMini extends MachineToNodeMetadataFixture {
+         protected void customizeBuilder(NodeMetadataBuilder nodeMetadataBuilder) {
+            nodeMetadataBuilder.loginPort(2222);
+         }
+
+         protected String getHardwareId() {
+            return "mini";
+         }
+
+         protected OsFamily getOsFamily() {
+            return OsFamily.LINUX;
+         }
+
+         protected void expectBoxConfig(BoxConfig boxConfig) {
+            EasyMock.expect(boxConfig.getKey(VagrantConstants.KEY_SSH_PORT)).andReturn(Optional.of("2222"));
+         }
+      }
+      new MachineToNodeMetadataLinuxMini().doTest();
+   }
+
+   @Test
+   public void testDefaultSshPort() {
+      class MachineToNodeMetadataLinuxMini extends MachineToNodeMetadataFixture {
+
+         protected OsFamily getOsFamily() {
+            return OsFamily.LINUX;
+         }
+
+         protected void expectBoxConfig(BoxConfig boxConfig) {
+            EasyMock.expect(boxConfig.getKey(VagrantConstants.KEY_SSH_PORT)).andReturn(Optional.<String>absent());
+         }
+
+         protected void customizeBuilder(NodeMetadataBuilder nodeMetadataBuilder) {
+            nodeMetadataBuilder.loginPort(22);
+         }
+
+         protected String getHardwareId() {
+            return "mini";
+         }
+      }
+      new MachineToNodeMetadataLinuxMini().doTest();
+   }
+
+   @Test
+   public void testAutoLinux() {
+      class MachineToNodeMetadataLinuxMini extends MachineToNodeMetadataFixture {
+         protected OsFamily getOsFamily() {
+            return OsFamily.LINUX;
+         }
+
+         protected void expectBoxConfig(BoxConfig boxConfig) {
+            EasyMock.expect(boxConfig.getKey(VagrantConstants.KEY_SSH_PORT)).andReturn(Optional.of("2222"));
+         }
+
+         protected void customizeBuilder(NodeMetadataBuilder nodeMetadataBuilder) {
+            nodeMetadataBuilder.loginPort(2222)
+               .hardware(new HardwareBuilder()
+                  .ids("automatic:cores=2.0;ram=1000")
+                  .processor(new Processor(2.0, 1))
+                  .ram(1000)
+                  .build());
+         }
+
+         @Override
+         protected Map<String, Object> getMachineConfig() {
+            return ImmutableMap.<String, Object>of(
+                  VagrantConstants.CONFIG_HARDWARE_ID, getHardwareId(),
+                  VagrantConstants.CONFIG_CPUS, "2.0",
+                  VagrantConstants.CONFIG_MEMORY, "1000");
+         }
+
+         protected String getHardwareId() {
+            return "automatic";
+         }
+      }
+      new MachineToNodeMetadataLinuxMini().doTest();
+   }
+
+   @Test
+   public void testMiniWin() {
+      class MachineToNodeMetadataLinuxMini extends MachineToNodeMetadataFixture {
+         protected void customizeBuilder(NodeMetadataBuilder nodeMetadataBuilder) {
+            nodeMetadataBuilder.loginPort(8899);
+         }
+
+         protected String getHardwareId() {
+            return "mini";
+         }
+
+         protected OsFamily getOsFamily() {
+            return OsFamily.WINDOWS;
+         }
+
+         protected void expectBoxConfig(BoxConfig boxConfig) {
+            EasyMock.expect(boxConfig.getKey(VagrantConstants.KEY_WINRM_PORT)).andReturn(Optional.of("8899"));
+         }
+      }
+      new MachineToNodeMetadataLinuxMini().doTest();
+   }
+
+   @Test
+   public void testDefaultWinrmPort() {
+      class MachineToNodeMetadataLinuxMini extends MachineToNodeMetadataFixture {
+
+         protected OsFamily getOsFamily() {
+            return OsFamily.WINDOWS;
+         }
+
+         protected void expectBoxConfig(BoxConfig boxConfig) {
+            EasyMock.expect(boxConfig.getKey(VagrantConstants.KEY_WINRM_PORT)).andReturn(Optional.<String>absent());
+         }
+
+         protected void customizeBuilder(NodeMetadataBuilder nodeMetadataBuilder) {
+            nodeMetadataBuilder.loginPort(5985);
+         }
+
+         protected String getHardwareId() {
+            return "mini";
+         }
+      }
+      new MachineToNodeMetadataLinuxMini().doTest();
+   }
+
+   @Test
+   public void testAutoWin() {
+      class MachineToNodeMetadataLinuxMini extends MachineToNodeMetadataFixture {
+         protected OsFamily getOsFamily() {
+            return OsFamily.WINDOWS;
+         }
+
+         protected void expectBoxConfig(BoxConfig boxConfig) {
+            EasyMock.expect(boxConfig.getKey(VagrantConstants.KEY_WINRM_PORT)).andReturn(Optional.of("8899"));
+         }
+
+         protected void customizeBuilder(NodeMetadataBuilder nodeMetadataBuilder) {
+            nodeMetadataBuilder.loginPort(8899)
+               .hardware(new HardwareBuilder()
+                  .ids("automatic:cores=2.0;ram=1000")
+                  .processor(new Processor(2.0, 1))
+                  .ram(1000)
+                  .build());
+         }
+
+         @Override
+         protected Map<String, Object> getMachineConfig() {
+            return ImmutableMap.<String, Object>of(
+                  VagrantConstants.CONFIG_HARDWARE_ID, getHardwareId(),
+                  VagrantConstants.CONFIG_CPUS, "2.0",
+                  VagrantConstants.CONFIG_MEMORY, "1000");
+         }
+
+         protected String getHardwareId() {
+            return "automatic";
+         }
+      }
+      new MachineToNodeMetadataLinuxMini().doTest();
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/test/java/org/jclouds/vagrant/functions/OutdatedBoxesFilterTest.java
----------------------------------------------------------------------
diff --git a/vagrant/src/test/java/org/jclouds/vagrant/functions/OutdatedBoxesFilterTest.java b/vagrant/src/test/java/org/jclouds/vagrant/functions/OutdatedBoxesFilterTest.java
new file mode 100644
index 0000000..7cf3f06
--- /dev/null
+++ b/vagrant/src/test/java/org/jclouds/vagrant/functions/OutdatedBoxesFilterTest.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.jclouds.vagrant.functions;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Collection;
+import java.util.Set;
+
+import org.testng.annotations.Test;
+import org.testng.collections.Sets;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+import vagrant.api.domain.Box;
+
+public class OutdatedBoxesFilterTest {
+   Collection<Box> UNFILTERED = ImmutableList.<Box>builder()
+         .add(new Box("CiscoCloud/microservices-infrastructure", "0.5", "virtualbox"))
+         .add(new Box("boxcutter/eval-win7x86-enterprise", "0", "virtualbox"))
+         .add(new Box("centos/7", "1603.01", "virtualbox"))
+         .add(new Box("centos/7", "1607.01", "virtualbox"))
+         .add(new Box("chef/centos-6.5", "1.0.0", "virtualbox"))
+         .add(new Box("chef/centos-7.0", "1.0.0", "virtualbox"))
+         .add(new Box("debian/jessie64", "8.4.0", "virtualbox"))
+         .add(new Box("debian/jessie64", "8.6.1", "virtualbox"))
+         .add(new Box("debian/wheezy64", "7.10.0", "virtualbox"))
+         .add(new Box("mitchellh/boot2docker", "1.2.0", "virtualbox"))
+         .add(new Box("nrel/CentOS-6.5-x86_64", "1.2.0", "virtualbox"))
+         .add(new Box("orlandohohmeier/toolchain", "0.5.1", "virtualbox"))
+         .add(new Box("playa_mesos_ubuntu_14.04", "0", "virtualbox"))
+         .add(new Box("snappy", "0", "virtualbox"))
+         .add(new Box("ubuntu/precise32", "20161208.0.0", "virtualbox"))
+         .add(new Box("ubuntu/precise64", "12.04.4", "virtualbox"))
+         .add(new Box("ubuntu/trusty64", "14.04", "virtualbox"))
+         .add(new Box("ubuntu/trusty64", "20160822.0.2", "virtualbox"))
+         .add(new Box("ubuntu/ubuntu-15.04-snappy-core-edge-amd64", "15.04.20150424", "virtualbox"))
+         .add(new Box("ubuntu/vivid64", "20150427.0.0", "virtualbox"))
+         .add(new Box("ubuntu/vivid64", "20150611.0.1", "virtualbox"))
+         .add(new Box("ubuntu/wily64", "20151106.0.0", "virtualbox"))
+         .add(new Box("ubuntu/wily64", "20160715.0.0", "virtualbox"))
+         .add(new Box("ubuntu/xenial64", "20160922.0.0", "virtualbox"))
+         .add(new Box("ubuntu/xenial64", "20161119.0.0", "virtualbox"))
+         .add(new Box("ubuntu/xenial64", "20161221.0.0", "virtualbox"))
+         .build();
+
+   Set<Box> FILTERED = ImmutableSet.<Box>builder()
+         .add(new Box("CiscoCloud/microservices-infrastructure", "0.5", "virtualbox"))
+         .add(new Box("boxcutter/eval-win7x86-enterprise", "0", "virtualbox"))
+         .add(new Box("centos/7", "1607.01", "virtualbox"))
+         .add(new Box("chef/centos-6.5", "1.0.0", "virtualbox"))
+         .add(new Box("chef/centos-7.0", "1.0.0", "virtualbox"))
+         .add(new Box("debian/jessie64", "8.6.1", "virtualbox"))
+         .add(new Box("debian/wheezy64", "7.10.0", "virtualbox"))
+         .add(new Box("mitchellh/boot2docker", "1.2.0", "virtualbox"))
+         .add(new Box("nrel/CentOS-6.5-x86_64", "1.2.0", "virtualbox"))
+         .add(new Box("orlandohohmeier/toolchain", "0.5.1", "virtualbox"))
+         .add(new Box("playa_mesos_ubuntu_14.04", "0", "virtualbox"))
+         .add(new Box("snappy", "0", "virtualbox"))
+         .add(new Box("ubuntu/precise32", "20161208.0.0", "virtualbox"))
+         .add(new Box("ubuntu/precise64", "12.04.4", "virtualbox"))
+         .add(new Box("ubuntu/trusty64", "20160822.0.2", "virtualbox"))
+         .add(new Box("ubuntu/ubuntu-15.04-snappy-core-edge-amd64", "15.04.20150424", "virtualbox"))
+         .add(new Box("ubuntu/vivid64", "20150611.0.1", "virtualbox"))
+         .add(new Box("ubuntu/wily64", "20160715.0.0", "virtualbox"))
+         .add(new Box("ubuntu/xenial64", "20161221.0.0", "virtualbox"))
+         .build();
+
+   @Test
+   public void testFilter() {
+      OutdatedBoxesFilter filter = new OutdatedBoxesFilter();
+      Collection<Box> actual = filter.apply(UNFILTERED);
+      assertEquals(Sets.newHashSet(actual), FILTERED, "Actual list: " + actual);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/test/java/org/jclouds/vagrant/internal/BoxConfigTest.java
----------------------------------------------------------------------
diff --git a/vagrant/src/test/java/org/jclouds/vagrant/internal/BoxConfigTest.java b/vagrant/src/test/java/org/jclouds/vagrant/internal/BoxConfigTest.java
new file mode 100644
index 0000000..631d813
--- /dev/null
+++ b/vagrant/src/test/java/org/jclouds/vagrant/internal/BoxConfigTest.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.internal;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.jclouds.vagrant.reference.VagrantConstants;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Optional;
+import com.google.common.io.Files;
+import com.google.common.io.Resources;
+
+import vagrant.api.domain.Box;
+
+public class BoxConfigTest {
+
+   private File vagrantHome;
+
+   @BeforeMethod
+   public void setUp() {
+      vagrantHome = new File(System.getProperty("java.io.tmpdir"), "jclouds/vagrant");
+   }
+
+   @Test
+   public void testKeys() throws IOException {
+      File boxFolder = new File(vagrantHome, "boxes/jclouds-VAGRANTSLASH-vagrant/0/virtualbox");
+      boxFolder.mkdirs();
+      File boxPath = new File(boxFolder, VagrantConstants.VAGRANTFILE);
+      Resources.asByteSource(getClass().getResource("/Vagrantfile.boxconfig")).copyTo(Files.asByteSink(boxPath));
+      
+      BoxConfig boxConfig = new BoxConfig.Factory().newInstance(vagrantHome, new Box("jclouds/vagrant", "0", "virtualbox"));
+      assertEquals(boxConfig.getKey(".non.existent"), Optional.absent());
+      assertEquals(boxConfig.getStringKey(".non.existent"), Optional.absent());
+      assertEquals(boxConfig.getKey(VagrantConstants.KEY_VM_GUEST), Optional.of(VagrantConstants.VM_GUEST_WINDOWS));
+      assertEquals(boxConfig.getKey(VagrantConstants.KEY_WINRM_USERNAME), Optional.of("\"jclouds-winrm\""));
+      assertEquals(boxConfig.getStringKey(VagrantConstants.KEY_WINRM_USERNAME), Optional.of("jclouds-winrm"));
+      assertEquals(boxConfig.getStringKey(VagrantConstants.KEY_WINRM_PASSWORD), Optional.of("password-winrm"));
+      assertEquals(boxConfig.getStringKey(VagrantConstants.KEY_WINRM_PORT), Optional.of("8899"));
+      assertEquals(boxConfig.getStringKey(VagrantConstants.KEY_SSH_USERNAME), Optional.of("jclouds-ssh"));
+      assertEquals(boxConfig.getStringKey(VagrantConstants.KEY_SSH_PASSWORD), Optional.of("password-ssh"));
+      assertEquals(boxConfig.getStringKey(VagrantConstants.KEY_SSH_PRIVATE_KEY_PATH), Optional.of("/path/to/private.key"));
+      assertEquals(boxConfig.getStringKey(VagrantConstants.KEY_SSH_PORT), Optional.of("2222"));
+
+      boxPath.delete();
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/test/java/org/jclouds/vagrant/internal/MachineConfigTest.java
----------------------------------------------------------------------
diff --git a/vagrant/src/test/java/org/jclouds/vagrant/internal/MachineConfigTest.java b/vagrant/src/test/java/org/jclouds/vagrant/internal/MachineConfigTest.java
new file mode 100644
index 0000000..eb968a6
--- /dev/null
+++ b/vagrant/src/test/java/org/jclouds/vagrant/internal/MachineConfigTest.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.vagrant.internal;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+import org.jclouds.JcloudsVersion;
+import org.jclouds.vagrant.reference.VagrantConstants;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.io.Files;
+import com.google.common.io.Resources;
+
+public class MachineConfigTest {
+   private static final Map<String, Object> CONFIG = ImmutableMap.<String, Object>builder()
+         .put("jcloudsVersion", "0.0.1")
+         .put("box", "jclouds/vagrant")
+         .put("osFamily", "ubuntu")
+         .put("hardwareId", "micro")
+         .put("memory", "512")
+         .put("cpus", "1")
+         .build();
+
+   private File machineFolder;
+   private MachineConfig machineConfig;
+   private File configPath;
+
+   @BeforeMethod
+   public void setUp() throws IOException {
+      File vagrantHome = new File(System.getProperty("java.io.tmpdir"), "jclouds/vagrant");
+      machineFolder = new File(vagrantHome, "jclouds");
+      File configFolder = new File(machineFolder, VagrantConstants.MACHINES_CONFIG_SUBFOLDER);
+      configFolder.mkdirs();
+      configPath = new File(configFolder, "vagrant" + VagrantConstants.MACHINES_CONFIG_EXTENSION);
+      machineConfig = new MachineConfig.Factory().newInstance(machineFolder, "vagrant");
+   }
+
+   @AfterMethod
+   public void tearDown() {
+      configPath.delete();
+   }
+
+   @Test
+   public void testRead() throws IOException {
+      Resources.asByteSource(getClass().getResource("/machine-config.yaml")).copyTo(Files.asByteSink(configPath));
+      assertEquals(machineConfig.load(), CONFIG);
+   }
+
+   @Test
+   public void testWrite() throws IOException {
+      machineConfig.save(CONFIG);
+      ByteArrayOutputStream actualBytes = new ByteArrayOutputStream();
+      Files.asByteSource(configPath).copyTo(actualBytes);
+
+      ByteArrayOutputStream expectedBytes = new ByteArrayOutputStream();
+      Resources.asByteSource(getClass().getResource("/machine-config.yaml")).copyTo(expectedBytes);
+
+      String actual = new String(actualBytes.toByteArray(), Charsets.UTF_8);
+      String expected = new String(expectedBytes.toByteArray(), Charsets.UTF_8);
+      assertEquals(actual, expected
+            .replace("jcloudsVersion: 0.0.1", "jcloudsVersion: " + JcloudsVersion.get().toString())
+            // Strip license headers
+            .replaceAll("(?m)^#.*", "")
+            .trim());
+   }
+
+   @Test
+   public void testUpdatesVersion() throws IOException {
+      machineConfig.save(CONFIG);
+      Map<String, Object> newConfig = machineConfig.load();
+      assertEquals(newConfig.get(VagrantConstants.CONFIG_JCLOUDS_VERSION), JcloudsVersion.get().toString());
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/test/java/org/jclouds/vagrant/internal/VagrantNodeRegistryTest.java
----------------------------------------------------------------------
diff --git a/vagrant/src/test/java/org/jclouds/vagrant/internal/VagrantNodeRegistryTest.java b/vagrant/src/test/java/org/jclouds/vagrant/internal/VagrantNodeRegistryTest.java
new file mode 100644
index 0000000..fb4950c
--- /dev/null
+++ b/vagrant/src/test/java/org/jclouds/vagrant/internal/VagrantNodeRegistryTest.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.jclouds.vagrant.internal;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+
+import java.io.File;
+import java.util.concurrent.TimeUnit;
+
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.ImageBuilder;
+import org.jclouds.compute.domain.OperatingSystem;
+import org.jclouds.compute.domain.OsFamily;
+import org.jclouds.vagrant.domain.VagrantNode;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableList;
+
+public class VagrantNodeRegistryTest {
+   private static class TestTimeSupplier implements Supplier<Long> {
+      long time = System.currentTimeMillis();
+
+      @Override
+      public Long get() {
+         return time;
+      }
+
+      public void advanceTime(long add) {
+         time += add;
+      }
+
+   }
+
+   @Test
+   public void testNodeRegistry() {
+      TestTimeSupplier timeSupplier = new TestTimeSupplier();
+      VagrantNodeRegistry registry = new VagrantNodeRegistry(timeSupplier);
+      OperatingSystem os = new OperatingSystem(OsFamily.UNRECOGNIZED, "Jclouds OS", "10", "x64", "Jclouds Test Image", true);
+      Image image = new ImageBuilder()
+            .ids("jclouds/box")
+            .operatingSystem(os)
+            .status(Image.Status.AVAILABLE)
+            .build();
+
+      ImmutableList<String> networks = ImmutableList.of("172.28.128.3");
+      VagrantNode node = VagrantNode.builder()
+            .setPath(new File("/path/to/machine"))
+            .setId("vagrant/node")
+            .setGroup("vagrant")
+            .setName("node")
+            .setImage(image)
+            .setNetworks(networks)
+            .setHostname("vagrant-node")
+            .build();
+
+      assertNull(registry.get(node.id()));
+      registry.add(node);
+      assertEquals(registry.get(node.id()), node);
+      registry.onTerminated(node);
+      assertEquals(registry.get(node.id()), node);
+      timeSupplier.advanceTime(TimeUnit.MINUTES.toMillis(10));
+      assertNull(registry.get(node.id()));
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/test/java/org/jclouds/vagrant/internal/VagrantOutputRecorderTest.java
----------------------------------------------------------------------
diff --git a/vagrant/src/test/java/org/jclouds/vagrant/internal/VagrantOutputRecorderTest.java b/vagrant/src/test/java/org/jclouds/vagrant/internal/VagrantOutputRecorderTest.java
new file mode 100644
index 0000000..c74bb54
--- /dev/null
+++ b/vagrant/src/test/java/org/jclouds/vagrant/internal/VagrantOutputRecorderTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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 org.testng.Assert.assertEquals;
+
+import org.testng.annotations.Test;
+
+import vagrant.api.CommandIOListener;
+
+public class VagrantOutputRecorderTest {
+   protected static final String INPUT = "vagrant up";
+   protected static final String OUT1 = "1482768916,f99,metadata,provider,virtualbox\n";
+   protected static final String OUT2 = "1482768916,,ui,info,Bringing machine ";
+   protected static final String OUT3 = "'f99' up with 'virtualbox' provider...\n";
+   protected static final String OUT4 = "1482768916,f99,action,up,sta";
+
+   private static CommandIOListener nopIOListener = new CommandIOListener() {
+      @Override
+      public void onInput(String input) {
+      }
+
+      @Override
+      public void onOutput(String output) {
+      }
+   };
+
+   @Test
+   public void testOutputRecorder() {
+      VagrantOutputRecorder outputRecorder = new VagrantOutputRecorder(nopIOListener);
+      outputRecorder.record();
+      assertEquals(outputRecorder.stopRecording(), "");
+      outputRecorder.record();
+      outputRecorder.onInput("vagrant up");
+      assertEquals(outputRecorder.stopRecording(), "");
+      outputRecorder.record();
+      outputRecorder.onOutput(OUT1);
+      outputRecorder.onOutput(OUT2);
+      outputRecorder.onOutput(OUT3 + OUT4);
+      assertEquals(outputRecorder.stopRecording(), OUT1 + OUT2 + OUT3 + OUT4);
+      outputRecorder.onOutput(OUT1);
+      assertEquals(outputRecorder.stopRecording(), "");
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/test/java/org/jclouds/vagrant/internal/VagrantWireLoggerTest.java
----------------------------------------------------------------------
diff --git a/vagrant/src/test/java/org/jclouds/vagrant/internal/VagrantWireLoggerTest.java b/vagrant/src/test/java/org/jclouds/vagrant/internal/VagrantWireLoggerTest.java
new file mode 100644
index 0000000..66b89a5
--- /dev/null
+++ b/vagrant/src/test/java/org/jclouds/vagrant/internal/VagrantWireLoggerTest.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.vagrant.internal;
+
+import java.io.InputStream;
+
+import org.easymock.Capture;
+import org.easymock.EasyMock;
+import org.jclouds.http.internal.HttpWire;
+import org.testng.annotations.Test;
+
+public class VagrantWireLoggerTest {
+   private static final String INPUT = VagrantOutputRecorderTest.INPUT;
+   private static final String OUT1 = VagrantOutputRecorderTest.OUT1;
+   private static final String OUT2 = VagrantOutputRecorderTest.OUT2;
+   private static final String OUT3 = VagrantOutputRecorderTest.OUT3;
+   private static final String OUT4 = VagrantOutputRecorderTest.OUT4;
+
+   @Test
+   public void testWireLogger() {
+      HttpWire httpWire = EasyMock.createMock(HttpWire.class);
+      Capture<InputStream> wireInCapture = new Capture<InputStream>();
+      EasyMock.expect(httpWire.input(EasyMock.capture(wireInCapture))).andReturn(null);
+      EasyMock.expect(httpWire.output(OUT1)).andReturn(OUT1);
+      EasyMock.expect(httpWire.output(OUT2 + OUT3)).andReturn(OUT2 + OUT3);
+      EasyMock.expect(httpWire.output(OUT4)).andReturn(OUT4);
+
+      EasyMock.replay(httpWire);
+
+      VagrantWireLogger wireLogger = new VagrantWireLogger(httpWire);
+      wireLogger.onInput(INPUT);
+      wireLogger.onInput(null);
+      wireLogger.onOutput(OUT1);
+      wireLogger.onOutput(OUT2);
+      wireLogger.onOutput(OUT3 + OUT4);
+      wireLogger.onOutput(null);
+      
+      EasyMock.verify(httpWire);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/test/java/org/jclouds/vagrant/strategy/VagrantDefaultImageCredentialsTest.java
----------------------------------------------------------------------
diff --git a/vagrant/src/test/java/org/jclouds/vagrant/strategy/VagrantDefaultImageCredentialsTest.java b/vagrant/src/test/java/org/jclouds/vagrant/strategy/VagrantDefaultImageCredentialsTest.java
new file mode 100644
index 0000000..ea29835
--- /dev/null
+++ b/vagrant/src/test/java/org/jclouds/vagrant/strategy/VagrantDefaultImageCredentialsTest.java
@@ -0,0 +1,179 @@
+/*
+ * 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.strategy;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Map;
+
+import org.easymock.EasyMock;
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.ImageBuilder;
+import org.jclouds.compute.domain.OperatingSystem;
+import org.jclouds.compute.domain.OsFamily;
+import org.jclouds.domain.Credentials;
+import org.jclouds.domain.LoginCredentials;
+import org.jclouds.vagrant.internal.BoxConfig;
+import org.jclouds.vagrant.reference.VagrantConstants;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Optional;
+
+public class VagrantDefaultImageCredentialsTest {
+
+   @Test
+   public void testCredentials() {
+      LoginCredentials creds = LoginCredentials.builder()
+            .user("vagrant")
+            .password("vagrant")
+            .noPrivateKey()
+            .build();
+
+      @SuppressWarnings("unchecked")
+      Map<String, Credentials> credentialStore = EasyMock.createMock(Map.class);
+      BoxConfig.Factory boxConfigFactory = EasyMock.createMock(BoxConfig.Factory.class);
+
+      EasyMock.replay(credentialStore, boxConfigFactory);
+
+      VagrantDefaultImageCredentials defaultImageCredentials =
+            new VagrantDefaultImageCredentials(creds, credentialStore, boxConfigFactory);
+
+      OperatingSystem os = new OperatingSystem(OsFamily.LINUX, "Jclouds OS", "10", "x64", "Jclouds Test Image", true);
+      Image image = new ImageBuilder()
+            .ids("jclouds/box")
+            .operatingSystem(os)
+            .status(org.jclouds.compute.domain.Image.Status.AVAILABLE)
+            .build();
+
+      LoginCredentials actualCreds = defaultImageCredentials.apply(image);
+
+      EasyMock.verify(credentialStore, boxConfigFactory);
+
+      assertEquals(actualCreds, creds);
+   }
+
+   @Test
+   public void testCredentialStore() {
+      LoginCredentials creds = LoginCredentials.builder()
+            .user("vagrant")
+            .password("vagrant")
+            .noPrivateKey()
+            .build();
+
+      OperatingSystem os = new OperatingSystem(OsFamily.LINUX, "Jclouds OS", "10", "x64", "Jclouds Test Image", true);
+      Image image = new ImageBuilder()
+            .ids("jclouds/box")
+            .operatingSystem(os)
+            .status(org.jclouds.compute.domain.Image.Status.AVAILABLE)
+            .build();
+
+      @SuppressWarnings("unchecked")
+      Map<String, Credentials> credentialStore = EasyMock.createMock(Map.class);
+      EasyMock.expect(credentialStore.containsKey("image#" + image.getId())).andReturn(Boolean.TRUE);
+      EasyMock.expect(credentialStore.get("image#" + image.getId())).andReturn(creds);
+
+      BoxConfig.Factory boxConfigFactory = EasyMock.createMock(BoxConfig.Factory.class);
+
+      EasyMock.replay(credentialStore, boxConfigFactory);
+
+      VagrantDefaultImageCredentials defaultImageCredentials =
+            new VagrantDefaultImageCredentials(null, credentialStore, boxConfigFactory);
+
+      LoginCredentials actualCreds = defaultImageCredentials.apply(image);
+
+      EasyMock.verify(credentialStore, boxConfigFactory);
+
+      assertEquals(actualCreds, creds);
+   }
+
+   @Test
+   public void testWinrmCredentials() {
+      LoginCredentials creds = LoginCredentials.builder()
+            .user("jclouds-user")
+            .password("jclouds-pass")
+            .noPrivateKey()
+            .build();
+
+      OperatingSystem os = new OperatingSystem(OsFamily.WINDOWS, "Jclouds OS", "10", "x64", "Jclouds Test Image", true);
+      Image image = new ImageBuilder()
+            .ids("jclouds/box")
+            .operatingSystem(os)
+            .status(org.jclouds.compute.domain.Image.Status.AVAILABLE)
+            .build();
+
+      @SuppressWarnings("unchecked")
+      Map<String, Credentials> credentialStore = EasyMock.createMock(Map.class);
+      EasyMock.expect(credentialStore.containsKey("image#" + image.getId())).andReturn(Boolean.FALSE);
+
+      BoxConfig boxConfig = EasyMock.createMock(BoxConfig.class);
+      EasyMock.expect(boxConfig.getStringKey(VagrantConstants.KEY_WINRM_USERNAME)).andReturn(Optional.of(creds.getUser()));
+      EasyMock.expect(boxConfig.getStringKey(VagrantConstants.KEY_WINRM_PASSWORD)).andReturn(Optional.of(creds.getOptionalPassword().get()));
+
+      BoxConfig.Factory boxConfigFactory = EasyMock.createMock(BoxConfig.Factory.class);
+      EasyMock.expect(boxConfigFactory.newInstance(image)).andReturn(boxConfig);
+
+      EasyMock.replay(credentialStore, boxConfig, boxConfigFactory);
+
+      VagrantDefaultImageCredentials defaultImageCredentials =
+            new VagrantDefaultImageCredentials(null, credentialStore, boxConfigFactory);
+
+      LoginCredentials actualCreds = defaultImageCredentials.apply(image);
+
+      EasyMock.verify(credentialStore, boxConfigFactory);
+
+      assertEquals(actualCreds, creds);
+   }
+
+   @Test
+   public void testSshCredentials() {
+      LoginCredentials creds = LoginCredentials.builder()
+            .user("jclouds-user")
+            .password("jclouds-pass")
+            .noPrivateKey()
+            .build();
+
+      OperatingSystem os = new OperatingSystem(OsFamily.LINUX, "Jclouds OS", "10", "x64", "Jclouds Test Image", true);
+      Image image = new ImageBuilder()
+            .ids("jclouds/box")
+            .operatingSystem(os)
+            .status(org.jclouds.compute.domain.Image.Status.AVAILABLE)
+            .build();
+
+      @SuppressWarnings("unchecked")
+      Map<String, Credentials> credentialStore = EasyMock.createMock(Map.class);
+      EasyMock.expect(credentialStore.containsKey("image#" + image.getId())).andReturn(Boolean.FALSE);
+
+      BoxConfig boxConfig = EasyMock.createMock(BoxConfig.class);
+      EasyMock.expect(boxConfig.getStringKey(VagrantConstants.KEY_SSH_USERNAME)).andReturn(Optional.of(creds.getUser()));
+      EasyMock.expect(boxConfig.getStringKey(VagrantConstants.KEY_SSH_PASSWORD)).andReturn(Optional.of(creds.getOptionalPassword().get()));
+      EasyMock.expect(boxConfig.getStringKey(VagrantConstants.KEY_SSH_PRIVATE_KEY_PATH)).andReturn(Optional.<String>absent());
+
+      BoxConfig.Factory boxConfigFactory = EasyMock.createMock(BoxConfig.Factory.class);
+      EasyMock.expect(boxConfigFactory.newInstance(image)).andReturn(boxConfig);
+
+      EasyMock.replay(credentialStore, boxConfig, boxConfigFactory);
+
+      VagrantDefaultImageCredentials defaultImageCredentials =
+            new VagrantDefaultImageCredentials(null, credentialStore, boxConfigFactory);
+
+      LoginCredentials actualCreds = defaultImageCredentials.apply(image);
+
+      EasyMock.verify(credentialStore, boxConfigFactory);
+
+      assertEquals(actualCreds, creds);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/test/resources/Vagrantfile.boxconfig
----------------------------------------------------------------------
diff --git a/vagrant/src/test/resources/Vagrantfile.boxconfig b/vagrant/src/test/resources/Vagrantfile.boxconfig
new file mode 100644
index 0000000..9f09642
--- /dev/null
+++ b/vagrant/src/test/resources/Vagrantfile.boxconfig
@@ -0,0 +1,25 @@
+# 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.
+
+Vagrant.configure(2) do |config|
+  config.vm.guest = :windows
+  config.winrm.username = "jclouds-winrm"
+  config.winrm.password = "password-winrm"
+  config.winrm.port = "8899"
+  config.ssh.username = "jclouds-ssh"
+  config.ssh.password = "password-ssh"
+  config.ssh.private_key_path = "/path/to/private.key"
+  config.ssh.port = "2222"
+end
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/test/resources/logback-test.xml
----------------------------------------------------------------------
diff --git a/vagrant/src/test/resources/logback-test.xml b/vagrant/src/test/resources/logback-test.xml
new file mode 100644
index 0000000..72e46d0
--- /dev/null
+++ b/vagrant/src/test/resources/logback-test.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+<!--
+
+    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.
+
+-->
+<configuration scan="false">
+    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <Pattern>%d %-5p [%c] [%thread] %m%n</Pattern>
+        </encoder>
+    </appender>
+
+    <root level="INFO">
+        <appender-ref ref="console"/>
+    </root>
+
+    <logger name="org.jclouds" level="INFO" />
+    <logger name="org.jclouds.vagrant" level="DEBUG" />
+    <logger name="jclouds.wire" level="DEBUG" />
+    <logger name="jclouds.headers" level="DEBUG" />
+    <logger name="ch.qos.logback" level="WARN" />
+
+</configuration>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/fadcd5d9/vagrant/src/test/resources/machine-config.yaml
----------------------------------------------------------------------
diff --git a/vagrant/src/test/resources/machine-config.yaml b/vagrant/src/test/resources/machine-config.yaml
new file mode 100644
index 0000000..f95642f
--- /dev/null
+++ b/vagrant/src/test/resources/machine-config.yaml
@@ -0,0 +1,20 @@
+# 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.
+jcloudsVersion: 0.0.1
+box: jclouds/vagrant
+osFamily: ubuntu
+hardwareId: micro
+memory: 512
+cpus: 1
\ No newline at end of file