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/05/22 08:25:32 UTC

[08/12] jclouds git commit: Add Packet ComputeAdapter

Add Packet ComputeAdapter

- add CreateSshKeysThenCreateNodes strategy
- add PacketComputeServiceLiveTest
- add PacketTemplateBuilderLiveTest
- refactor deviceApi.actions into separate operations
- fix Device.State
- remove overriden live test as it is now pushed up to jclouds/jclouds
- improve comments on the adapter


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

Branch: refs/heads/master
Commit: d65f9ca621ca4f5aa75cbf7786176af41913e7f3
Parents: 3696412
Author: Andrea Turli <an...@gmail.com>
Authored: Thu Jan 26 13:09:07 2017 +0100
Committer: Andrea Turli <an...@gmail.com>
Committed: Tue Jan 31 15:55:06 2017 +0100

----------------------------------------------------------------------
 .../org/jclouds/packet/PacketApiMetadata.java   |   9 +-
 .../compute/PacketComputeServiceAdapter.java    | 184 ++++++++++++++++
 .../PacketComputeServiceContextModule.java      | 151 +++++++++++++
 .../compute/functions/DeviceStateToStatus.java  |   9 +-
 .../compute/functions/DeviceToNodeMetadata.java |   2 +-
 .../compute/options/PacketTemplateOptions.java  | 158 ++++++++++++++
 .../strategy/CreateSshKeysThenCreateNodes.java  | 215 +++++++++++++++++++
 .../java/org/jclouds/packet/domain/Device.java  |   2 +-
 .../org/jclouds/packet/features/DeviceApi.java  |  35 ++-
 .../PacketComputeProviderMetadataTest.java      |  30 +++
 .../compute/PacketComputeServiceLiveTest.java   |  83 +++++++
 .../compute/PacketTemplateBuilderLiveTest.java  |  55 +++++
 .../compute/internal/BasePacketApiLiveTest.java |   8 +
 .../packet/features/DeviceApiLiveTest.java      |  15 +-
 .../packet/features/DeviceApiMockTest.java      |  30 +--
 .../packet/features/FacilityApiLiveTest.java    |   4 +-
 providers/packet/src/test/resources/rescue.json |   3 -
 17 files changed, 944 insertions(+), 49 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/d65f9ca6/providers/packet/src/main/java/org/jclouds/packet/PacketApiMetadata.java
----------------------------------------------------------------------
diff --git a/providers/packet/src/main/java/org/jclouds/packet/PacketApiMetadata.java b/providers/packet/src/main/java/org/jclouds/packet/PacketApiMetadata.java
index e05685e..5893ea0 100644
--- a/providers/packet/src/main/java/org/jclouds/packet/PacketApiMetadata.java
+++ b/providers/packet/src/main/java/org/jclouds/packet/PacketApiMetadata.java
@@ -20,6 +20,8 @@ import java.net.URI;
 import java.util.Properties;
 
 import org.jclouds.apis.ApiMetadata;
+import org.jclouds.compute.ComputeServiceContext;
+import org.jclouds.packet.compute.config.PacketComputeServiceContextModule;
 import org.jclouds.packet.config.PacketComputeParserModule;
 import org.jclouds.packet.config.PacketHttpApiModule;
 import org.jclouds.rest.internal.BaseHttpApiMetadata;
@@ -29,6 +31,8 @@ import com.google.inject.Module;
 
 import static org.jclouds.compute.config.ComputeServiceProperties.TEMPLATE;
 import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED;
+import static org.jclouds.reflect.Reflection2.typeToken;
 
 /**
  * Implementation of {@link ApiMetadata} for Packet API
@@ -52,6 +56,7 @@ public class PacketApiMetadata extends BaseHttpApiMetadata<PacketApi> {
       Properties properties = BaseHttpApiMetadata.defaultProperties();
       properties.put(TEMPLATE, "osFamily=UBUNTU,os64Bit=true,osVersionMatches=16.*");
       properties.put(TIMEOUT_NODE_RUNNING, 300000); // 5 mins
+      properties.put(TIMEOUT_NODE_SUSPENDED, 300000); // 5 mins
       return properties;
    }
 
@@ -66,11 +71,11 @@ public class PacketApiMetadata extends BaseHttpApiMetadata<PacketApi> {
                  .defaultEndpoint("https://api.packet.net")
                  .defaultProperties(PacketApiMetadata.defaultProperties())
                  .version("1")
-                 //.view(typeToken(ComputeServiceContext.class))
+                 .view(typeToken(ComputeServiceContext.class))
                  .defaultModules(ImmutableSet.<Class<? extends Module>>builder()
                          .add(PacketHttpApiModule.class)
                          .add(PacketComputeParserModule.class)
-                         //.add(PacketComputeServiceContextModule.class)
+                         .add(PacketComputeServiceContextModule.class)
                          .build());
       }
 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/d65f9ca6/providers/packet/src/main/java/org/jclouds/packet/compute/PacketComputeServiceAdapter.java
----------------------------------------------------------------------
diff --git a/providers/packet/src/main/java/org/jclouds/packet/compute/PacketComputeServiceAdapter.java b/providers/packet/src/main/java/org/jclouds/packet/compute/PacketComputeServiceAdapter.java
new file mode 100644
index 0000000..3672b14
--- /dev/null
+++ b/providers/packet/src/main/java/org/jclouds/packet/compute/PacketComputeServiceAdapter.java
@@ -0,0 +1,184 @@
+/*
+ * 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.packet.compute;
+
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.jclouds.compute.ComputeServiceAdapter;
+import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.domain.Credentials;
+import org.jclouds.location.Provider;
+import org.jclouds.logging.Logger;
+import org.jclouds.packet.PacketApi;
+import org.jclouds.packet.compute.options.PacketTemplateOptions;
+import org.jclouds.packet.domain.BillingCycle;
+import org.jclouds.packet.domain.Device;
+import org.jclouds.packet.domain.Facility;
+import org.jclouds.packet.domain.OperatingSystem;
+import org.jclouds.packet.domain.Plan;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
+import com.google.common.collect.Iterables;
+
+import static com.google.common.collect.Iterables.contains;
+import static com.google.common.collect.Iterables.filter;
+
+/**
+ * defines the connection between the {@link org.jclouds.packet.PacketApi} implementation and
+ * the jclouds {@link org.jclouds.compute.ComputeService}
+ */
+@Singleton
+public class PacketComputeServiceAdapter implements ComputeServiceAdapter<Device, Plan, OperatingSystem, Facility> {
+
+   @Resource
+   @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   private final PacketApi api;
+   private final String projectId;
+
+   @Inject
+   PacketComputeServiceAdapter(PacketApi api, @Provider final Supplier<Credentials> creds) {
+      this.api = api;
+      this.projectId = creds.get().identity;
+   }
+
+   @Override
+   public NodeAndInitialCredentials<Device> createNodeWithGroupEncodedIntoName(String group, String name, Template template) {
+
+      PacketTemplateOptions templateOptions = template.getOptions().as(PacketTemplateOptions.class);
+      Map<String, String> features = templateOptions.getFeatures();
+      BillingCycle billingCycle = BillingCycle.fromValue(templateOptions.getBillingCycle());
+      boolean locked = templateOptions.isLocked();
+      String userdata = templateOptions.getUserData();
+      Set<String> tags = templateOptions.getTags();
+
+      String plan = template.getHardware().getId();
+      String facility = template.getLocation().getId();
+      String operatingSystem = template.getImage().getId();
+
+      Device device = api.deviceApi(projectId).create(
+              Device.CreateDevice.builder()
+                      .hostname(name)
+                      .plan(plan)
+                      .billingCycle(billingCycle.value())
+                      .facility(facility)
+                      .features(features)
+                      .operatingSystem(operatingSystem)
+                      .locked(locked)
+                      .userdata(userdata)
+                      .tags(tags)
+                      .build()
+              );
+
+      // Any new servers you deploy to projects you are a collaborator on will have your project and personal SSH keys, if defined.
+      // If no SSH keys are defined in your account, jclouds will generate one usiing CreateSshKeysThenCreateNodes 
+      // so that it will add it to the device with the default mechanism.
+
+      // Safe to pass null credentials here, as jclouds will default populate
+      // the node with the default credentials from the image, or the ones in
+      // the options, if provided.
+      return new NodeAndInitialCredentials<Device>(device, device.id(), null);
+   }
+
+   @Override
+   public Iterable<Plan> listHardwareProfiles() {
+      return Iterables.filter(api.planApi().list().concat(), new Predicate<Plan>() {
+         @Override
+         public boolean apply(Plan input) {
+            return input.line().equals("baremetal");
+         }
+      });
+   }
+
+   @Override
+   public Iterable<OperatingSystem> listImages() {
+      return api.operatingSystemApi().list().concat();
+   }
+
+   @Override
+   public OperatingSystem getImage(final String id) {
+      Optional<OperatingSystem> firstInterestingOperatingSystem = api
+              .operatingSystemApi().list()
+              .concat()
+              .firstMatch(new Predicate<OperatingSystem>() {
+                 @Override
+                 public boolean apply(OperatingSystem input) {
+                    return input.slug().equals(id);
+                 }
+              });
+      if (!firstInterestingOperatingSystem.isPresent()) {
+         throw new IllegalStateException("Cannot find image with the required slug " + id);
+      }
+      return firstInterestingOperatingSystem.get();
+   }
+
+   @Override
+   public Iterable<Facility> listLocations() {
+      return api.facilityApi().list().concat();
+   }
+
+   @Override
+   public Device getNode(String id) {
+      return api.deviceApi(projectId).get(id);
+   }
+
+   @Override
+   public void destroyNode(String id) {
+      api.deviceApi(projectId).delete(id);
+   }
+
+   @Override
+   public void rebootNode(String id) {
+      api.deviceApi(projectId).reboot(id);
+   }
+
+   @Override
+   public void resumeNode(String id) {
+      api.deviceApi(projectId).powerOn(id);
+   }
+
+   @Override
+   public void suspendNode(String id) {
+      api.deviceApi(projectId).powerOff(id);
+   }
+
+   @Override
+   public Iterable<Device> listNodes() {
+     return api.deviceApi(projectId).list().concat();
+   }
+
+   @Override
+   public Iterable<Device> listNodesByIds(final Iterable<String> ids) {
+      return filter(listNodes(), new Predicate<Device>() {
+         @Override
+         public boolean apply(Device device) {
+            return contains(ids, String.valueOf(device.id()));
+         }
+      });
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/d65f9ca6/providers/packet/src/main/java/org/jclouds/packet/compute/config/PacketComputeServiceContextModule.java
----------------------------------------------------------------------
diff --git a/providers/packet/src/main/java/org/jclouds/packet/compute/config/PacketComputeServiceContextModule.java b/providers/packet/src/main/java/org/jclouds/packet/compute/config/PacketComputeServiceContextModule.java
new file mode 100644
index 0000000..0a64f43
--- /dev/null
+++ b/providers/packet/src/main/java/org/jclouds/packet/compute/config/PacketComputeServiceContextModule.java
@@ -0,0 +1,151 @@
+/*
+ * 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.packet.compute.config;
+
+import org.jclouds.compute.ComputeServiceAdapter;
+import org.jclouds.compute.config.ComputeServiceAdapterContextModule;
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.options.TemplateOptions;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.compute.strategy.CreateNodesInGroupThenAddToSet;
+import org.jclouds.domain.Credentials;
+import org.jclouds.domain.Location;
+import org.jclouds.location.Provider;
+import org.jclouds.packet.PacketApi;
+import org.jclouds.packet.compute.PacketComputeServiceAdapter;
+import org.jclouds.packet.compute.functions.DeviceStateToStatus;
+import org.jclouds.packet.compute.functions.DeviceToNodeMetadata;
+import org.jclouds.packet.compute.functions.FacilityToLocation;
+import org.jclouds.packet.compute.functions.OperatingSystemToImage;
+import org.jclouds.packet.compute.functions.PlanToHardware;
+import org.jclouds.packet.compute.options.PacketTemplateOptions;
+import org.jclouds.packet.compute.strategy.CreateSshKeysThenCreateNodes;
+import org.jclouds.packet.domain.Device;
+import org.jclouds.packet.domain.Facility;
+import org.jclouds.packet.domain.OperatingSystem;
+import org.jclouds.packet.domain.Plan;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
+import com.google.inject.Provides;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Named;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_TERMINATED;
+import static org.jclouds.util.Predicates2.retry;
+
+public class PacketComputeServiceContextModule extends
+        ComputeServiceAdapterContextModule<Device, Plan, OperatingSystem, Facility> {
+
+   @SuppressWarnings("unchecked")
+   @Override
+   protected void configure() {
+      super.configure();
+
+      bind(new TypeLiteral<ComputeServiceAdapter<Device, Plan, OperatingSystem, Facility>>() {
+      }).to(PacketComputeServiceAdapter.class);
+
+      bind(new TypeLiteral<Function<Device, NodeMetadata>>() {
+      }).to(DeviceToNodeMetadata.class);
+      bind(new TypeLiteral<Function<Plan, Hardware>>() {
+      }).to(PlanToHardware.class);
+      bind(new TypeLiteral<Function<OperatingSystem, Image>>() {
+      }).to(OperatingSystemToImage.class);
+      bind(new TypeLiteral<Function<Facility, Location>>() {
+      }).to(FacilityToLocation.class);
+      bind(new TypeLiteral<Function<Device.State, NodeMetadata.Status>>() {
+      }).to(DeviceStateToStatus.class);
+      install(new LocationsFromComputeServiceAdapterModule<Device, Plan, OperatingSystem, Facility>() {
+      });
+      bind(TemplateOptions.class).to(PacketTemplateOptions.class);
+      bind(CreateNodesInGroupThenAddToSet.class).to(CreateSshKeysThenCreateNodes.class);
+   }
+
+   @Provides
+   @Named(TIMEOUT_NODE_RUNNING)
+   protected Predicate<String> provideDeviceRunningPredicate(final PacketApi api,
+                                                             @Provider final Supplier<Credentials> creds,
+                                                             ComputeServiceConstants.Timeouts timeouts,
+                                                             ComputeServiceConstants.PollPeriod pollPeriod) {
+      return retry(new DeviceInStatusPredicate(api, creds.get().identity, Device.State.ACTIVE), timeouts.nodeRunning,
+              pollPeriod.pollInitialPeriod, pollPeriod.pollMaxPeriod);
+   }
+
+   @Provides
+   @Named(TIMEOUT_NODE_SUSPENDED)
+   protected Predicate<String> provideDeviceSuspendedPredicate(final PacketApi api, @Provider final Supplier<Credentials> creds, ComputeServiceConstants.Timeouts timeouts,
+                                                                 ComputeServiceConstants.PollPeriod pollPeriod) {
+      return retry(new DeviceInStatusPredicate(api, creds.get().identity, Device.State.INACTIVE), timeouts.nodeSuspended,
+              pollPeriod.pollInitialPeriod, pollPeriod.pollMaxPeriod);
+   }
+   
+   @Provides
+   @Named(TIMEOUT_NODE_TERMINATED)
+   protected Predicate<String> provideDeviceTerminatedPredicate(final PacketApi api, @Provider final Supplier<Credentials> creds, ComputeServiceConstants.Timeouts timeouts,
+                                                                 ComputeServiceConstants.PollPeriod pollPeriod) {
+      return retry(new DeviceTerminatedPredicate(api, creds.get().identity), timeouts.nodeTerminated, pollPeriod.pollInitialPeriod,
+              pollPeriod.pollMaxPeriod);
+   }
+
+   @VisibleForTesting
+   static class DeviceInStatusPredicate implements Predicate<String> {
+
+      private final PacketApi api;
+      private final String projectId;
+      private final Device.State state;
+
+      public DeviceInStatusPredicate(PacketApi api, String projectId, Device.State state) {
+         this.api = checkNotNull(api, "api must not be null");
+         this.projectId = checkNotNull(projectId, "projectId must not be null");
+         this.state = checkNotNull(state, "state must not be null");
+      }
+
+      @Override
+      public boolean apply(String input) {
+         checkNotNull(input, "device id");
+         Device device = api.deviceApi(projectId).get(input);
+         return device != null && state == device.state();
+      }
+   }
+
+   @VisibleForTesting
+   static class DeviceTerminatedPredicate implements Predicate<String> {
+
+      private final PacketApi api;
+      private final String projectId;
+
+      public DeviceTerminatedPredicate(PacketApi api, String projectId) {
+         this.api = checkNotNull(api, "api must not be null");
+         this.projectId = checkNotNull(projectId, "projectId must not be null");
+      }
+
+      @Override
+      public boolean apply(String input) {
+         checkNotNull(input, "device id");
+         Device device = api.deviceApi(projectId).get(input);
+         return device == null;
+      }
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/d65f9ca6/providers/packet/src/main/java/org/jclouds/packet/compute/functions/DeviceStateToStatus.java
----------------------------------------------------------------------
diff --git a/providers/packet/src/main/java/org/jclouds/packet/compute/functions/DeviceStateToStatus.java b/providers/packet/src/main/java/org/jclouds/packet/compute/functions/DeviceStateToStatus.java
index acb1f00..7606bf5 100644
--- a/providers/packet/src/main/java/org/jclouds/packet/compute/functions/DeviceStateToStatus.java
+++ b/providers/packet/src/main/java/org/jclouds/packet/compute/functions/DeviceStateToStatus.java
@@ -33,8 +33,13 @@ public class DeviceStateToStatus implements Function<Device.State, Status> {
 
    private static final Function<Device.State, Status> toPortableStatus = Functions.forMap(
          ImmutableMap.<Device.State, Status> builder()
-               .put(Device.State.PROVISIONING, Status.PENDING)
-               .put(Device.State.ACTIVE, Status.RUNNING)
+                 .put(Device.State.PROVISIONING, Status.PENDING)
+                 .put(Device.State.POWERING_ON, Status.PENDING)
+                 .put(Device.State.POWERING_OFF, Status.PENDING)
+                 .put(Device.State.REBOOTING, Status.PENDING)
+                 .put(Device.State.QUEUED, Status.PENDING)
+                 .put(Device.State.INACTIVE, Status.SUSPENDED)
+                 .put(Device.State.ACTIVE, Status.RUNNING)
                .build(),
          Status.UNRECOGNIZED);
 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/d65f9ca6/providers/packet/src/main/java/org/jclouds/packet/compute/functions/DeviceToNodeMetadata.java
----------------------------------------------------------------------
diff --git a/providers/packet/src/main/java/org/jclouds/packet/compute/functions/DeviceToNodeMetadata.java b/providers/packet/src/main/java/org/jclouds/packet/compute/functions/DeviceToNodeMetadata.java
index 74d9d95..ec222fc 100644
--- a/providers/packet/src/main/java/org/jclouds/packet/compute/functions/DeviceToNodeMetadata.java
+++ b/providers/packet/src/main/java/org/jclouds/packet/compute/functions/DeviceToNodeMetadata.java
@@ -69,7 +69,7 @@ public class DeviceToNodeMetadata implements Function<Device, NodeMetadata> {
       return new NodeMetadataBuilder()
               .ids(input.id())
               .name(input.hostname())
-              .hostname(input.hostname())
+              .hostname(String.format("%s", input.hostname()))  
               .group(groupNamingConvention.extractGroup(input.hostname()))
               .location(facilityToLocation.apply(input.facility()))
               .hardware(planToHardware.apply(input.plan()))

http://git-wip-us.apache.org/repos/asf/jclouds/blob/d65f9ca6/providers/packet/src/main/java/org/jclouds/packet/compute/options/PacketTemplateOptions.java
----------------------------------------------------------------------
diff --git a/providers/packet/src/main/java/org/jclouds/packet/compute/options/PacketTemplateOptions.java b/providers/packet/src/main/java/org/jclouds/packet/compute/options/PacketTemplateOptions.java
new file mode 100644
index 0000000..30cef45
--- /dev/null
+++ b/providers/packet/src/main/java/org/jclouds/packet/compute/options/PacketTemplateOptions.java
@@ -0,0 +1,158 @@
+/*
+ * 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.packet.compute.options;
+
+import java.util.Map;
+
+import org.jclouds.compute.options.TemplateOptions;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Objects.ToStringHelper;
+import com.google.common.collect.ImmutableMap;
+
+import static com.google.common.base.Objects.equal;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Custom options for the Packet API.
+ */
+public class PacketTemplateOptions extends TemplateOptions implements Cloneable {
+
+   private Map<String, String> features = ImmutableMap.of();
+   private boolean locked = false;
+   private String billingCycle = "hourly";
+   private String userData = "";
+
+   public PacketTemplateOptions features(Map<String, String> features) {
+      this.features = ImmutableMap.copyOf(checkNotNull(features, "features cannot be null"));
+      return this;
+   }
+   
+   public PacketTemplateOptions locked(boolean locked) {
+      this.locked = locked;
+      return this;
+   }
+
+   public PacketTemplateOptions billingCycle(String billingCycle) {
+      this.billingCycle = billingCycle;
+      return this;
+   }
+   
+   public PacketTemplateOptions userData(String userData) {
+      this.userData = userData;
+      return this;
+   }
+
+   public Map<String, String> getFeatures() {
+      return features;
+   }
+   public boolean isLocked() {
+      return locked;
+   }
+   public String getBillingCycle() {
+      return billingCycle;
+   }
+   public String getUserData() {
+      return userData;
+   }
+
+   @Override
+   public PacketTemplateOptions clone() {
+      PacketTemplateOptions options = new PacketTemplateOptions();
+      copyTo(options);
+      return options;
+   }
+
+   @Override
+   public void copyTo(TemplateOptions to) {
+      super.copyTo(to);
+      if (to instanceof PacketTemplateOptions) {
+         PacketTemplateOptions eTo = PacketTemplateOptions.class.cast(to);
+         eTo.features(features);
+         eTo.locked(locked);
+         eTo.billingCycle(billingCycle);
+         eTo.userData(userData);
+      }
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(super.hashCode(), features, locked, billingCycle, userData);
+   }
+
+   @Override
+   public boolean equals(Object obj) {
+      if (this == obj) {
+         return true;
+      }
+      if (!super.equals(obj)) {
+         return false;
+      }
+      if (getClass() != obj.getClass()) {
+         return false;
+      }
+      PacketTemplateOptions other = (PacketTemplateOptions) obj;
+      return super.equals(other) && equal(this.locked, other.locked) && equal(this.billingCycle, other.billingCycle) && equal(this.userData, other.userData) && equal(this.features, other.features);
+   }
+
+   @Override
+   public ToStringHelper string() {
+      ToStringHelper toString = super.string().omitNullValues();
+      if (!features.isEmpty()) {
+         toString.add("features", features);
+      }      toString.add("locked", locked);
+      toString.add("billingCycle", billingCycle);
+      toString.add("userData", userData);
+      return toString;
+   }
+
+   public static class Builder {
+
+      /**
+       * @see PacketTemplateOptions#features
+       */
+      public static PacketTemplateOptions features(Map<String, String> features) {
+         PacketTemplateOptions options = new PacketTemplateOptions();
+         return options.features(features);
+      }
+      
+      /**
+       * @see PacketTemplateOptions#locked
+       */
+      public static PacketTemplateOptions locked(boolean locked) {
+         PacketTemplateOptions options = new PacketTemplateOptions();
+         return options.locked(locked);
+      }
+
+      /**
+       * @see PacketTemplateOptions#billingCycle
+       */
+      public static PacketTemplateOptions billingCycle(String billingCycle) {
+         PacketTemplateOptions options = new PacketTemplateOptions();
+         return options.billingCycle(billingCycle);
+      }
+      
+      /**
+       * @see PacketTemplateOptions#userData
+       */
+      public static PacketTemplateOptions userData(String userData) {
+         PacketTemplateOptions options = new PacketTemplateOptions();
+         return options.userData(userData);
+      }
+
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/d65f9ca6/providers/packet/src/main/java/org/jclouds/packet/compute/strategy/CreateSshKeysThenCreateNodes.java
----------------------------------------------------------------------
diff --git a/providers/packet/src/main/java/org/jclouds/packet/compute/strategy/CreateSshKeysThenCreateNodes.java b/providers/packet/src/main/java/org/jclouds/packet/compute/strategy/CreateSshKeysThenCreateNodes.java
new file mode 100644
index 0000000..3a5a02a
--- /dev/null
+++ b/providers/packet/src/main/java/org/jclouds/packet/compute/strategy/CreateSshKeysThenCreateNodes.java
@@ -0,0 +1,215 @@
+/*
+ * 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.packet.compute.strategy;
+
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.jclouds.Constants;
+import org.jclouds.compute.config.CustomizationResponse;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.functions.GroupNamingConvention;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.compute.strategy.CreateNodeWithGroupEncodedIntoName;
+import org.jclouds.compute.strategy.CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap;
+import org.jclouds.compute.strategy.ListNodesStrategy;
+import org.jclouds.compute.strategy.impl.CreateNodesWithGroupEncodedIntoNameThenAddToSet;
+import org.jclouds.logging.Logger;
+import org.jclouds.packet.PacketApi;
+import org.jclouds.packet.compute.options.PacketTemplateOptions;
+import org.jclouds.packet.domain.SshKey;
+import org.jclouds.ssh.SshKeyPairGenerator;
+import org.jclouds.ssh.SshKeys;
+
+import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Throwables.propagate;
+import static com.google.common.collect.Iterables.get;
+import static com.google.common.collect.Iterables.size;
+
+@Singleton
+public class CreateSshKeysThenCreateNodes extends CreateNodesWithGroupEncodedIntoNameThenAddToSet {
+
+    @Resource
+    @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+    protected Logger logger = Logger.NULL;
+
+    private final PacketApi api;
+    private final SshKeyPairGenerator keyGenerator;
+
+    @Inject
+    protected CreateSshKeysThenCreateNodes(
+            CreateNodeWithGroupEncodedIntoName addNodeWithGroupStrategy,
+            ListNodesStrategy listNodesStrategy,
+            GroupNamingConvention.Factory namingConvention,
+            @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor,
+            CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.Factory customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory,
+            PacketApi api, SshKeyPairGenerator keyGenerator) {
+        super(addNodeWithGroupStrategy, listNodesStrategy, namingConvention, userExecutor,
+                customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory);
+        this.api = api;
+        this.keyGenerator = keyGenerator;
+    }
+
+    @Override
+    public Map<?, ListenableFuture<Void>> execute(String group, int count, Template template,
+                                                  Set<NodeMetadata> goodNodes, Map<NodeMetadata, Exception> badNodes,
+                                                  Multimap<NodeMetadata, CustomizationResponse> customizationResponses) {
+
+        PacketTemplateOptions options = template.getOptions().as(PacketTemplateOptions.class);
+        Set<String> generatedSshKeyIds = Sets.newHashSet();
+
+        // If no key has been configured, generate a key pair
+        if (Strings.isNullOrEmpty(options.getPublicKey())) {
+            generateKeyPairAndAddKeyToSet(options, generatedSshKeyIds, group);
+        }
+
+        // If there is a script to run in the node, make sure a private key has
+        // been configured so jclouds will be able to access the node
+        if (options.getRunScript() != null && Strings.isNullOrEmpty(options.getLoginPrivateKey())) {
+            logger.warn(">> A runScript has been configured but no SSH key has been provided."
+                    + " Authentication will delegate to the ssh-agent");
+        }
+
+        // If there is a key configured, then make sure there is a key pair for it
+        if (!Strings.isNullOrEmpty(options.getPublicKey())) {
+            createKeyPairForPublicKeyInOptionsAndAddToSet(options, generatedSshKeyIds);
+        }
+
+        Map<?, ListenableFuture<Void>> responses = super.execute(group, count, template, goodNodes, badNodes,
+                customizationResponses);
+
+        // Key pairs in Packet are only required to create the devices. They aren't used anymore so it is better
+        // to delete the auto-generated key pairs at this point where we know exactly which ones have been
+        // auto-generated by jclouds.
+        registerAutoGeneratedKeyPairCleanupCallbacks(responses, generatedSshKeyIds);
+
+        return responses;
+    }
+
+    private void createKeyPairForPublicKeyInOptionsAndAddToSet(PacketTemplateOptions options,
+                                                               Set<String> generatedSshKeyIds) {
+        logger.debug(">> checking if the key pair already exists...");
+
+        PublicKey userKey;
+        Iterable<String> parts = Splitter.on(' ').split(options.getPublicKey());
+        checkArgument(size(parts) >= 2, "bad format, should be: ssh-rsa AAAAB3...");
+        String type = get(parts, 0);
+
+        try {
+            if ("ssh-rsa".equals(type)) {
+                RSAPublicKeySpec spec = SshKeys.publicKeySpecFromOpenSSH(options.getPublicKey());
+                userKey = KeyFactory.getInstance("RSA").generatePublic(spec);
+            } else {
+                throw new IllegalArgumentException("bad format, ssh-rsa is only supported");
+            }
+        } catch (InvalidKeySpecException ex) {
+            throw propagate(ex);
+        } catch (NoSuchAlgorithmException ex) {
+            throw propagate(ex);
+        }
+      String label = computeFingerprint(userKey);
+      SshKey key = api.sshKeyApi().get(label);
+
+      if (key == null) {
+         logger.debug(">> key pair not found. creating a new key pair %s ...", label);
+         SshKey newKey = api.sshKeyApi().create(label, options.getPublicKey());
+         logger.debug(">> key pair created! %s", newKey);
+      } else {
+         logger.debug(">> key pair found! %s", key);
+         generatedSshKeyIds.add(key.id());
+      }
+    }
+
+    private void generateKeyPairAndAddKeyToSet(PacketTemplateOptions options, Set<String> generatedSshKeyIds, String prefix) {
+        logger.debug(">> creating default keypair for node...");
+
+        Map<String, String> defaultKeys = keyGenerator.get();
+
+        SshKey sshKey = api.sshKeyApi().create(prefix + System.getProperty("user.name"), defaultKeys.get("public"));
+        generatedSshKeyIds.add(sshKey.id());
+        logger.debug(">> keypair created! %s", sshKey);
+
+        // If a private key has not been explicitly set, configure the generated one
+        if (Strings.isNullOrEmpty(options.getLoginPrivateKey())) {
+            options.overrideLoginPrivateKey(defaultKeys.get("private"));
+        }
+    }
+
+    private void registerAutoGeneratedKeyPairCleanupCallbacks(Map<?, ListenableFuture<Void>> responses,
+                                                              final Set<String> generatedSshKeyIds) {
+        // The Futures.allAsList fails immediately if some of the futures fail. The Futures.successfulAsList, however,
+        // returns a list containing the results or 'null' for those futures that failed. We want to wait for all them
+        // (even if they fail), so better use the latter form.
+        ListenableFuture<List<Void>> aggregatedResponses = Futures.successfulAsList(responses.values());
+
+        // Key pairs must be cleaned up after all futures completed (even if some failed).
+        Futures.addCallback(aggregatedResponses, new FutureCallback<List<Void>>() {
+            @Override
+            public void onSuccess(List<Void> result) {
+                cleanupAutoGeneratedKeyPairs(generatedSshKeyIds);
+            }
+
+            @Override
+            public void onFailure(Throwable t) {
+                cleanupAutoGeneratedKeyPairs(generatedSshKeyIds);
+            }
+
+            private void cleanupAutoGeneratedKeyPairs(Set<String> generatedSshKeyIds) {
+                logger.debug(">> cleaning up auto-generated key pairs...");
+                for (String sshKeyId : generatedSshKeyIds) {
+                    try {
+                        api.sshKeyApi().delete(sshKeyId);
+                    } catch (Exception ex) {
+                        logger.warn(">> could not delete key pair %s: %s", sshKeyId, ex.getMessage());
+                    }
+                }
+            }
+        }, userExecutor);
+    }
+
+    private static String computeFingerprint(PublicKey key) {
+        if (key instanceof RSAPublicKey) {
+            RSAPublicKey rsaKey = (RSAPublicKey) key;
+            return SshKeys.fingerprint(rsaKey.getPublicExponent(), rsaKey.getModulus());
+        } else {
+            throw new IllegalArgumentException("Only RSA keys are supported");
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/d65f9ca6/providers/packet/src/main/java/org/jclouds/packet/domain/Device.java
----------------------------------------------------------------------
diff --git a/providers/packet/src/main/java/org/jclouds/packet/domain/Device.java b/providers/packet/src/main/java/org/jclouds/packet/domain/Device.java
index 951d938..18f5d95 100644
--- a/providers/packet/src/main/java/org/jclouds/packet/domain/Device.java
+++ b/providers/packet/src/main/java/org/jclouds/packet/domain/Device.java
@@ -38,7 +38,7 @@ import static com.google.common.base.Preconditions.checkArgument;
 public abstract class Device {
 
     public enum State {
-        PROVISIONING, QUEUED, ACTIVE;
+        PROVISIONING, QUEUED, ACTIVE, REBOOTING, POWERING_OFF, POWERING_ON, INACTIVE;
 
         public static State fromValue(String value) {
             Optional<State> state = Enums.getIfPresent(State.class, value.toUpperCase());

http://git-wip-us.apache.org/repos/asf/jclouds/blob/d65f9ca6/providers/packet/src/main/java/org/jclouds/packet/features/DeviceApi.java
----------------------------------------------------------------------
diff --git a/providers/packet/src/main/java/org/jclouds/packet/features/DeviceApi.java b/providers/packet/src/main/java/org/jclouds/packet/features/DeviceApi.java
index 9f4c672..cae9305 100644
--- a/providers/packet/src/main/java/org/jclouds/packet/features/DeviceApi.java
+++ b/providers/packet/src/main/java/org/jclouds/packet/features/DeviceApi.java
@@ -40,7 +40,6 @@ import org.jclouds.http.functions.ParseJson;
 import org.jclouds.javax.annotation.Nullable;
 import org.jclouds.json.Json;
 import org.jclouds.packet.PacketApi;
-import org.jclouds.packet.domain.ActionType;
 import org.jclouds.packet.domain.Device;
 import org.jclouds.packet.domain.Href;
 import org.jclouds.packet.domain.internal.PaginatedCollection;
@@ -49,8 +48,7 @@ import org.jclouds.packet.filters.AddApiVersionToRequest;
 import org.jclouds.packet.filters.AddXAuthTokenToRequest;
 import org.jclouds.rest.annotations.BinderParam;
 import org.jclouds.rest.annotations.Fallback;
-import org.jclouds.rest.annotations.MapBinder;
-import org.jclouds.rest.annotations.PayloadParam;
+import org.jclouds.rest.annotations.Payload;
 import org.jclouds.rest.annotations.RequestFilters;
 import org.jclouds.rest.annotations.ResponseParser;
 import org.jclouds.rest.annotations.Transform;
@@ -60,13 +58,13 @@ import com.google.common.base.Function;
 import com.google.common.base.Optional;
 import com.google.inject.TypeLiteral;
 
-@Path("/projects/{projectId}/devices")
 @Consumes(MediaType.APPLICATION_JSON)
 @RequestFilters({AddXAuthTokenToRequest.class, AddApiVersionToRequest.class})
 public interface DeviceApi {
 
    @Named("device:list")
    @GET
+   @Path("/projects/{projectId}/devices")
    @ResponseParser(ParseDevices.class)
    @Transform(ParseDevices.ToPagedIterable.class)
    @Fallback(Fallbacks.EmptyPagedIterableOnNotFoundOr404.class)
@@ -74,6 +72,7 @@ public interface DeviceApi {
 
    @Named("device:list")
    @GET
+   @Path("/projects/{projectId}/devices")
    @ResponseParser(ParseDevices.class)
    @Fallback(Fallbacks.EmptyIterableWithMarkerOnNotFoundOr404.class)
    IterableWithMarker<Device> list(ListOptions options);
@@ -122,27 +121,43 @@ public interface DeviceApi {
 
    @Named("device:create")
    @POST
+   @Path("/projects/{projectId}/devices")
    @Produces(MediaType.APPLICATION_JSON)
    Device create(@BinderParam(BindToJsonPayload.class) Device.CreateDevice device);
 
 
    @Named("device:get")
    @GET
-   @Path("/{id}")
+   @Path("/devices/{id}")
    @Fallback(NullOnNotFoundOr404.class)
    @Nullable
    Device get(@PathParam("id") String id);
 
    @Named("device:delete")
    @DELETE
-   @Path("/{id}")
+   @Path("/devices/{id}")
    @Fallback(VoidOnNotFoundOr404.class)
    void delete(@PathParam("id") String id);
 
-   @Named("device:actions")
+   @Named("device:powerOff")
    @POST
-   @Path("/{id}/actions")
-   @MapBinder(BindToJsonPayload.class)
-   void actions(@PathParam("id") String id, @PayloadParam("type") ActionType type);
+   @Produces(MediaType.APPLICATION_JSON)
+   @Path("/devices/{id}/actions")
+   @Payload("{\"type\":\"power_off\"}")
+   void powerOff(@PathParam("id") String id);
+
+   @Named("device:powerOn")
+   @POST
+   @Produces(MediaType.APPLICATION_JSON)
+   @Path("/devices/{id}/actions")
+   @Payload("{\"type\":\"power_on\"}")
+   void powerOn(@PathParam("id") String id);
+   
+   @Named("device:reboot")
+   @POST
+   @Produces(MediaType.APPLICATION_JSON)
+   @Path("/devices/{id}/actions")
+   @Payload("{\"type\":\"reboot\"}")
+   void reboot(@PathParam("id") String id);
 
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/d65f9ca6/providers/packet/src/test/java/org/jclouds/packet/compute/PacketComputeProviderMetadataTest.java
----------------------------------------------------------------------
diff --git a/providers/packet/src/test/java/org/jclouds/packet/compute/PacketComputeProviderMetadataTest.java b/providers/packet/src/test/java/org/jclouds/packet/compute/PacketComputeProviderMetadataTest.java
new file mode 100644
index 0000000..a7f0c97
--- /dev/null
+++ b/providers/packet/src/test/java/org/jclouds/packet/compute/PacketComputeProviderMetadataTest.java
@@ -0,0 +1,30 @@
+/*
+ * 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.packet.compute;
+
+import org.jclouds.packet.PacketApiMetadata;
+import org.jclouds.packet.PacketProviderMetadata;
+import org.jclouds.providers.internal.BaseProviderMetadataTest;
+import org.testng.annotations.Test;
+
+@Test(groups = "unit", testName = "PacketComputeProviderMetadataTest")
+public class PacketComputeProviderMetadataTest extends BaseProviderMetadataTest {
+
+   public PacketComputeProviderMetadataTest() {
+      super(new PacketProviderMetadata(), new PacketApiMetadata());
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/d65f9ca6/providers/packet/src/test/java/org/jclouds/packet/compute/PacketComputeServiceLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/packet/src/test/java/org/jclouds/packet/compute/PacketComputeServiceLiveTest.java b/providers/packet/src/test/java/org/jclouds/packet/compute/PacketComputeServiceLiveTest.java
new file mode 100644
index 0000000..dc10ecd
--- /dev/null
+++ b/providers/packet/src/test/java/org/jclouds/packet/compute/PacketComputeServiceLiveTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.packet.compute;
+
+import java.util.Properties;
+
+import org.jclouds.compute.ComputeServiceContext;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.internal.BaseComputeServiceLiveTest;
+import org.jclouds.rest.AuthorizationException;
+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;
+
+/**
+ * Live tests for the {@link org.jclouds.compute.ComputeService} integration.
+ */
+@Test(groups = "live", singleThreaded = true, testName = "PacketComputeServiceLiveTest")
+public class PacketComputeServiceLiveTest extends BaseComputeServiceLiveTest {
+
+   public PacketComputeServiceLiveTest() {
+      provider = "packet";
+   }
+
+   @Override
+   protected Module getSshModule() {
+      return new SshjSshClientModule();
+   }
+
+   @Override
+   @Test(expectedExceptions = AuthorizationException.class)
+   public void testCorrectAuthException() throws Exception {
+      ComputeServiceContext context = null;
+      try {
+         Properties overrides = setupProperties();
+         overrides.setProperty(provider + ".identity", "MOM:MA");
+         overrides.setProperty(provider + ".credential", "MIA");
+         context = newBuilder()
+                 .modules(ImmutableSet.of(getLoggingModule(), credentialStoreModule))
+                 .overrides(overrides)
+                 .build(ComputeServiceContext.class);
+         // replace listNodes with listImages as it doesn't require `projectId`
+         context.getComputeService().listImages();
+      } catch (AuthorizationException e) {
+         throw e;
+      } catch (RuntimeException e) {
+         e.printStackTrace();
+         throw e;
+      } finally {
+         if (context != null)
+            context.close();
+      }
+   }
+
+   @Override
+   public void testOptionToNotBlock() throws Exception {
+      // Packet ComputeService implementation has to block until the node
+      // is provisioned, to be able to return it.
+   }
+
+   @Override
+   protected void checkUserMetadataContains(NodeMetadata node, ImmutableMap<String, String> userMetadata) {
+      // The Packet API does not return the user data
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/d65f9ca6/providers/packet/src/test/java/org/jclouds/packet/compute/PacketTemplateBuilderLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/packet/src/test/java/org/jclouds/packet/compute/PacketTemplateBuilderLiveTest.java b/providers/packet/src/test/java/org/jclouds/packet/compute/PacketTemplateBuilderLiveTest.java
new file mode 100644
index 0000000..5faddde
--- /dev/null
+++ b/providers/packet/src/test/java/org/jclouds/packet/compute/PacketTemplateBuilderLiveTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.packet.compute;
+
+import java.io.IOException;
+import java.util.Set;
+
+import org.jclouds.compute.domain.OsFamily;
+import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.internal.BaseTemplateBuilderLiveTest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+
+import static org.jclouds.compute.util.ComputeServiceUtils.getCores;
+import static org.testng.Assert.assertEquals;
+
+@Test(groups = "live", testName = "PacketTemplateBuilderLiveTest")
+public class PacketTemplateBuilderLiveTest extends BaseTemplateBuilderLiveTest {
+
+   public PacketTemplateBuilderLiveTest() {
+      provider = "packet";
+   }
+
+   @Test
+   @Override
+   public void testDefaultTemplateBuilder() throws IOException {
+      Template defaultTemplate = view.getComputeService().templateBuilder().build();
+      assert defaultTemplate.getImage().getOperatingSystem().getVersion().startsWith("16.") : defaultTemplate
+            .getImage().getOperatingSystem().getVersion();
+      assertEquals(defaultTemplate.getImage().getOperatingSystem().is64Bit(), true);
+      assertEquals(defaultTemplate.getImage().getOperatingSystem().getFamily(), OsFamily.UBUNTU);
+      assertEquals(getCores(defaultTemplate.getHardware()), 1.0d);
+   }
+
+   @Override
+   protected Set<String> getIso3166Codes() {
+      return ImmutableSet.of("US-CA", "US-NJ", "NL", "JP");
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/d65f9ca6/providers/packet/src/test/java/org/jclouds/packet/compute/internal/BasePacketApiLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/packet/src/test/java/org/jclouds/packet/compute/internal/BasePacketApiLiveTest.java b/providers/packet/src/test/java/org/jclouds/packet/compute/internal/BasePacketApiLiveTest.java
index 6c8cf63..aacf945 100644
--- a/providers/packet/src/test/java/org/jclouds/packet/compute/internal/BasePacketApiLiveTest.java
+++ b/providers/packet/src/test/java/org/jclouds/packet/compute/internal/BasePacketApiLiveTest.java
@@ -31,12 +31,14 @@ import com.google.inject.TypeLiteral;
 import com.google.inject.name.Names;
 
 import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED;
 import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_TERMINATED;
 import static org.testng.Assert.assertTrue;
 
 public class BasePacketApiLiveTest extends BaseApiLiveTest<PacketApi> {
 
    private Predicate<String> deviceRunning;
+   private Predicate<String> deviceSuspended;
    private Predicate<String> deviceTerminated;
 
    public BasePacketApiLiveTest() {
@@ -57,6 +59,8 @@ public class BasePacketApiLiveTest extends BaseApiLiveTest<PacketApi> {
       Injector injector = newBuilder().modules(modules).overrides(props).buildInjector();
       deviceRunning = injector.getInstance(Key.get(new TypeLiteral<Predicate<String>>(){},
             Names.named(TIMEOUT_NODE_RUNNING)));
+      deviceSuspended = injector.getInstance(Key.get(new TypeLiteral<Predicate<String>>(){},
+              Names.named(TIMEOUT_NODE_SUSPENDED)));
       deviceTerminated = injector.getInstance(Key.get(new TypeLiteral<Predicate<String>>(){},
               Names.named(TIMEOUT_NODE_TERMINATED)));
       return injector.getInstance(PacketApi.class);
@@ -66,6 +70,10 @@ public class BasePacketApiLiveTest extends BaseApiLiveTest<PacketApi> {
       assertTrue(deviceRunning.apply(deviceId), String.format("Device %s did not start in the configured timeout", deviceId));
    }
 
+   protected void assertNodeSuspended(String deviceId) {
+      assertTrue(deviceSuspended.apply(deviceId), String.format("Device %s was not suspended in the configured timeout", deviceId));
+   }
+   
    protected void assertNodeTerminated(String deviceId) {
       assertTrue(deviceTerminated.apply(deviceId), String.format("Device %s was not terminated in the configured timeout", deviceId));
    }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/d65f9ca6/providers/packet/src/test/java/org/jclouds/packet/features/DeviceApiLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/packet/src/test/java/org/jclouds/packet/features/DeviceApiLiveTest.java b/providers/packet/src/test/java/org/jclouds/packet/features/DeviceApiLiveTest.java
index 36c08f1..cc96ebd 100644
--- a/providers/packet/src/test/java/org/jclouds/packet/features/DeviceApiLiveTest.java
+++ b/providers/packet/src/test/java/org/jclouds/packet/features/DeviceApiLiveTest.java
@@ -20,7 +20,6 @@ import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.jclouds.packet.compute.internal.BasePacketApiLiveTest;
-import org.jclouds.packet.domain.ActionType;
 import org.jclouds.packet.domain.BillingCycle;
 import org.jclouds.packet.domain.Device;
 import org.jclouds.packet.domain.SshKey;
@@ -40,7 +39,7 @@ import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
 import static org.testng.util.Strings.isNullOrEmpty;
 
-@Test(groups = "live", testName = "DeviceApiLiveTest")
+@Test(groups = "live", singleThreaded = true, testName = "DeviceApiLiveTest")
 public class DeviceApiLiveTest extends BasePacketApiLiveTest {
 
    private SshKey sshKey;
@@ -81,22 +80,22 @@ public class DeviceApiLiveTest extends BasePacketApiLiveTest {
 
    @Test(groups = "live", dependsOnMethods = "testCreate")
    public void testReboot() {
-      api().actions(deviceId, ActionType.REBOOT);
+      api().reboot(deviceId);
       assertNodeRunning(deviceId);
    }
 
    @Test(groups = "live", dependsOnMethods = "testReboot")
    public void testPowerOff() {
-      api().actions(deviceId, ActionType.POWER_OFF);
-      assertNodeTerminated(deviceId);
+      api().powerOff(deviceId);
+      assertNodeSuspended(deviceId);
    }
 
    @Test(groups = "live", dependsOnMethods = "testPowerOff")
    public void testPowerOn() {
-      api().actions(deviceId, ActionType.POWER_ON);
+      api().powerOn(deviceId);
       assertNodeRunning(deviceId);
    }
-   
+
    @Test(dependsOnMethods = "testCreate")
    public void testList() {
       final AtomicInteger found = new AtomicInteger(0);
@@ -123,7 +122,7 @@ public class DeviceApiLiveTest extends BasePacketApiLiveTest {
       assertTrue(found.get() > 0, "Expected some devices to be returned");
    }
 
-   @Test(dependsOnMethods = "testList", alwaysRun = true)
+   @Test(dependsOnMethods = "testPowerOn", alwaysRun = true)
    public void testDelete() throws InterruptedException {
       if (deviceId != null) {
          api().delete(deviceId);

http://git-wip-us.apache.org/repos/asf/jclouds/blob/d65f9ca6/providers/packet/src/test/java/org/jclouds/packet/features/DeviceApiMockTest.java
----------------------------------------------------------------------
diff --git a/providers/packet/src/test/java/org/jclouds/packet/features/DeviceApiMockTest.java b/providers/packet/src/test/java/org/jclouds/packet/features/DeviceApiMockTest.java
index 705955c..f53c206 100644
--- a/providers/packet/src/test/java/org/jclouds/packet/features/DeviceApiMockTest.java
+++ b/providers/packet/src/test/java/org/jclouds/packet/features/DeviceApiMockTest.java
@@ -17,7 +17,6 @@
 package org.jclouds.packet.features;
 
 import org.jclouds.packet.compute.internal.BasePacketApiMockTest;
-import org.jclouds.packet.domain.ActionType;
 import org.jclouds.packet.domain.BillingCycle;
 import org.jclouds.packet.domain.Device;
 import org.jclouds.packet.domain.Device.CreateDevice;
@@ -90,7 +89,7 @@ public class DeviceApiMockTest extends BasePacketApiMockTest {
       assertEquals(device, objectFromResource("/device.json", Device.class));
 
       assertEquals(server.getRequestCount(), 1);
-      assertSent(server, "GET", "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54/devices/1");
+      assertSent(server, "GET", "/devices/1");
    }
 
    public void testGetDeviceReturns404() throws InterruptedException {
@@ -101,7 +100,7 @@ public class DeviceApiMockTest extends BasePacketApiMockTest {
       assertNull(device);
 
       assertEquals(server.getRequestCount(), 1);
-      assertSent(server, "GET", "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54/devices/1");
+      assertSent(server, "GET", "/devices/1");
    }
 
    public void testCreateDevice() throws InterruptedException {
@@ -133,7 +132,7 @@ public class DeviceApiMockTest extends BasePacketApiMockTest {
       api.deviceApi("93907f48-adfe-43ed-ad89-0e6e83721a54").delete("1");
 
       assertEquals(server.getRequestCount(), 1);
-      assertSent(server, "DELETE", "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54/devices/1");
+      assertSent(server, "DELETE", "/devices/1");
    }
 
    public void testDeleteDeviceReturns404() throws InterruptedException {
@@ -142,43 +141,34 @@ public class DeviceApiMockTest extends BasePacketApiMockTest {
       api.deviceApi("93907f48-adfe-43ed-ad89-0e6e83721a54").delete("1");
 
       assertEquals(server.getRequestCount(), 1);
-      assertSent(server, "DELETE", "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54/devices/1");
+      assertSent(server, "DELETE", "/devices/1");
    }
 
    public void testActionPowerOn() throws InterruptedException {
       server.enqueue(jsonResponse("/power-on.json"));
 
-      api.deviceApi("93907f48-adfe-43ed-ad89-0e6e83721a54").actions("deviceId", ActionType.POWER_ON);
+      api.deviceApi("93907f48-adfe-43ed-ad89-0e6e83721a54").powerOn("deviceId");
 
       assertEquals(server.getRequestCount(), 1);
-      assertSent(server, "POST", "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54/devices/deviceId/actions");
+      assertSent(server, "POST", "/devices/deviceId/actions");
    }
 
    public void testActionPowerOff() throws InterruptedException {
       server.enqueue(jsonResponse("/power-off.json"));
 
-      api.deviceApi("93907f48-adfe-43ed-ad89-0e6e83721a54").actions("deviceId", ActionType.POWER_OFF);
+      api.deviceApi("93907f48-adfe-43ed-ad89-0e6e83721a54").powerOff("deviceId");
 
       assertEquals(server.getRequestCount(), 1);
-      assertSent(server, "POST", "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54/devices/deviceId/actions");
+      assertSent(server, "POST", "/devices/deviceId/actions");
    }
 
    public void testActionReboot() throws InterruptedException {
       server.enqueue(jsonResponse("/reboot.json"));
 
-      api.deviceApi("93907f48-adfe-43ed-ad89-0e6e83721a54").actions("deviceId", ActionType.REBOOT);
+      api.deviceApi("93907f48-adfe-43ed-ad89-0e6e83721a54").reboot("deviceId");
 
       assertEquals(server.getRequestCount(), 1);
-      assertSent(server, "POST", "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54/devices/deviceId/actions");
+      assertSent(server, "POST", "/devices/deviceId/actions");
    }
 
-   public void testActionRescue() throws InterruptedException {
-      server.enqueue(jsonResponse("/rescue.json"));
-
-      api.deviceApi("93907f48-adfe-43ed-ad89-0e6e83721a54").actions("deviceId", ActionType.RESCUE);
-
-      assertEquals(server.getRequestCount(), 1);
-      assertSent(server, "POST", "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54/devices/deviceId/actions");
-   }
-   
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/d65f9ca6/providers/packet/src/test/java/org/jclouds/packet/features/FacilityApiLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/packet/src/test/java/org/jclouds/packet/features/FacilityApiLiveTest.java b/providers/packet/src/test/java/org/jclouds/packet/features/FacilityApiLiveTest.java
index 95fc857..0ee90da 100644
--- a/providers/packet/src/test/java/org/jclouds/packet/features/FacilityApiLiveTest.java
+++ b/providers/packet/src/test/java/org/jclouds/packet/features/FacilityApiLiveTest.java
@@ -18,7 +18,7 @@ package org.jclouds.packet.features;
 
 import java.util.concurrent.atomic.AtomicInteger;
 
-import org.jclouds.packet.compute.internal.BasePacketApiMockTest;
+import org.jclouds.packet.compute.internal.BasePacketApiLiveTest;
 import org.jclouds.packet.domain.Facility;
 import org.testng.annotations.Test;
 
@@ -30,7 +30,7 @@ import static org.testng.Assert.assertTrue;
 import static org.testng.util.Strings.isNullOrEmpty;
 
 @Test(groups = "live", testName = "FacilityApiLiveTest")
-public class FacilityApiLiveTest extends BasePacketApiMockTest {
+public class FacilityApiLiveTest extends BasePacketApiLiveTest {
 
    public void testList() {
       final AtomicInteger found = new AtomicInteger(0);

http://git-wip-us.apache.org/repos/asf/jclouds/blob/d65f9ca6/providers/packet/src/test/resources/rescue.json
----------------------------------------------------------------------
diff --git a/providers/packet/src/test/resources/rescue.json b/providers/packet/src/test/resources/rescue.json
deleted file mode 100644
index 91ea877..0000000
--- a/providers/packet/src/test/resources/rescue.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-  "type": "rescue"
-}