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"
-}