Repository: jclouds-labs Updated Branches: refs/heads/2.0.x 66bed4b31 -> 5261cba28
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-labs/repo Commit: http://git-wip-us.apache.org/repos/asf/jclouds-labs/commit/38f0a50d Tree: http://git-wip-us.apache.org/repos/asf/jclouds-labs/tree/38f0a50d Diff: http://git-wip-us.apache.org/repos/asf/jclouds-labs/diff/38f0a50d Branch: refs/heads/2.0.x Commit: 38f0a50dc0bec474082e6ebb8fd0ae0cd48a1bb4 Parents: cbe306f Author: Andrea Turli <[email protected]> Authored: Thu Jan 26 13:09:07 2017 +0100 Committer: Ignasi Barrera <[email protected]> Committed: Thu Feb 2 15:23:25 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 +- packet/src/test/resources/rescue.json | 3 - 17 files changed, 944 insertions(+), 49 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/38f0a50d/packet/src/main/java/org/jclouds/packet/PacketApiMetadata.java ---------------------------------------------------------------------- diff --git a/packet/src/main/java/org/jclouds/packet/PacketApiMetadata.java b/packet/src/main/java/org/jclouds/packet/PacketApiMetadata.java index e05685e..5893ea0 100644 --- a/packet/src/main/java/org/jclouds/packet/PacketApiMetadata.java +++ b/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-labs/blob/38f0a50d/packet/src/main/java/org/jclouds/packet/compute/PacketComputeServiceAdapter.java ---------------------------------------------------------------------- diff --git a/packet/src/main/java/org/jclouds/packet/compute/PacketComputeServiceAdapter.java b/packet/src/main/java/org/jclouds/packet/compute/PacketComputeServiceAdapter.java new file mode 100644 index 0000000..3672b14 --- /dev/null +++ b/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-labs/blob/38f0a50d/packet/src/main/java/org/jclouds/packet/compute/config/PacketComputeServiceContextModule.java ---------------------------------------------------------------------- diff --git a/packet/src/main/java/org/jclouds/packet/compute/config/PacketComputeServiceContextModule.java b/packet/src/main/java/org/jclouds/packet/compute/config/PacketComputeServiceContextModule.java new file mode 100644 index 0000000..0a64f43 --- /dev/null +++ b/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-labs/blob/38f0a50d/packet/src/main/java/org/jclouds/packet/compute/functions/DeviceStateToStatus.java ---------------------------------------------------------------------- diff --git a/packet/src/main/java/org/jclouds/packet/compute/functions/DeviceStateToStatus.java b/packet/src/main/java/org/jclouds/packet/compute/functions/DeviceStateToStatus.java index acb1f00..7606bf5 100644 --- a/packet/src/main/java/org/jclouds/packet/compute/functions/DeviceStateToStatus.java +++ b/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-labs/blob/38f0a50d/packet/src/main/java/org/jclouds/packet/compute/functions/DeviceToNodeMetadata.java ---------------------------------------------------------------------- diff --git a/packet/src/main/java/org/jclouds/packet/compute/functions/DeviceToNodeMetadata.java b/packet/src/main/java/org/jclouds/packet/compute/functions/DeviceToNodeMetadata.java index 74d9d95..ec222fc 100644 --- a/packet/src/main/java/org/jclouds/packet/compute/functions/DeviceToNodeMetadata.java +++ b/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-labs/blob/38f0a50d/packet/src/main/java/org/jclouds/packet/compute/options/PacketTemplateOptions.java ---------------------------------------------------------------------- diff --git a/packet/src/main/java/org/jclouds/packet/compute/options/PacketTemplateOptions.java b/packet/src/main/java/org/jclouds/packet/compute/options/PacketTemplateOptions.java new file mode 100644 index 0000000..30cef45 --- /dev/null +++ b/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-labs/blob/38f0a50d/packet/src/main/java/org/jclouds/packet/compute/strategy/CreateSshKeysThenCreateNodes.java ---------------------------------------------------------------------- diff --git a/packet/src/main/java/org/jclouds/packet/compute/strategy/CreateSshKeysThenCreateNodes.java b/packet/src/main/java/org/jclouds/packet/compute/strategy/CreateSshKeysThenCreateNodes.java new file mode 100644 index 0000000..3a5a02a --- /dev/null +++ b/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-labs/blob/38f0a50d/packet/src/main/java/org/jclouds/packet/domain/Device.java ---------------------------------------------------------------------- diff --git a/packet/src/main/java/org/jclouds/packet/domain/Device.java b/packet/src/main/java/org/jclouds/packet/domain/Device.java index 951d938..18f5d95 100644 --- a/packet/src/main/java/org/jclouds/packet/domain/Device.java +++ b/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-labs/blob/38f0a50d/packet/src/main/java/org/jclouds/packet/features/DeviceApi.java ---------------------------------------------------------------------- diff --git a/packet/src/main/java/org/jclouds/packet/features/DeviceApi.java b/packet/src/main/java/org/jclouds/packet/features/DeviceApi.java index 9f4c672..cae9305 100644 --- a/packet/src/main/java/org/jclouds/packet/features/DeviceApi.java +++ b/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-labs/blob/38f0a50d/packet/src/test/java/org/jclouds/packet/compute/PacketComputeProviderMetadataTest.java ---------------------------------------------------------------------- diff --git a/packet/src/test/java/org/jclouds/packet/compute/PacketComputeProviderMetadataTest.java b/packet/src/test/java/org/jclouds/packet/compute/PacketComputeProviderMetadataTest.java new file mode 100644 index 0000000..a7f0c97 --- /dev/null +++ b/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-labs/blob/38f0a50d/packet/src/test/java/org/jclouds/packet/compute/PacketComputeServiceLiveTest.java ---------------------------------------------------------------------- diff --git a/packet/src/test/java/org/jclouds/packet/compute/PacketComputeServiceLiveTest.java b/packet/src/test/java/org/jclouds/packet/compute/PacketComputeServiceLiveTest.java new file mode 100644 index 0000000..dc10ecd --- /dev/null +++ b/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-labs/blob/38f0a50d/packet/src/test/java/org/jclouds/packet/compute/PacketTemplateBuilderLiveTest.java ---------------------------------------------------------------------- diff --git a/packet/src/test/java/org/jclouds/packet/compute/PacketTemplateBuilderLiveTest.java b/packet/src/test/java/org/jclouds/packet/compute/PacketTemplateBuilderLiveTest.java new file mode 100644 index 0000000..5faddde --- /dev/null +++ b/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-labs/blob/38f0a50d/packet/src/test/java/org/jclouds/packet/compute/internal/BasePacketApiLiveTest.java ---------------------------------------------------------------------- diff --git a/packet/src/test/java/org/jclouds/packet/compute/internal/BasePacketApiLiveTest.java b/packet/src/test/java/org/jclouds/packet/compute/internal/BasePacketApiLiveTest.java index 6c8cf63..aacf945 100644 --- a/packet/src/test/java/org/jclouds/packet/compute/internal/BasePacketApiLiveTest.java +++ b/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-labs/blob/38f0a50d/packet/src/test/java/org/jclouds/packet/features/DeviceApiLiveTest.java ---------------------------------------------------------------------- diff --git a/packet/src/test/java/org/jclouds/packet/features/DeviceApiLiveTest.java b/packet/src/test/java/org/jclouds/packet/features/DeviceApiLiveTest.java index 36c08f1..cc96ebd 100644 --- a/packet/src/test/java/org/jclouds/packet/features/DeviceApiLiveTest.java +++ b/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-labs/blob/38f0a50d/packet/src/test/java/org/jclouds/packet/features/DeviceApiMockTest.java ---------------------------------------------------------------------- diff --git a/packet/src/test/java/org/jclouds/packet/features/DeviceApiMockTest.java b/packet/src/test/java/org/jclouds/packet/features/DeviceApiMockTest.java index 705955c..f53c206 100644 --- a/packet/src/test/java/org/jclouds/packet/features/DeviceApiMockTest.java +++ b/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-labs/blob/38f0a50d/packet/src/test/java/org/jclouds/packet/features/FacilityApiLiveTest.java ---------------------------------------------------------------------- diff --git a/packet/src/test/java/org/jclouds/packet/features/FacilityApiLiveTest.java b/packet/src/test/java/org/jclouds/packet/features/FacilityApiLiveTest.java index 95fc857..0ee90da 100644 --- a/packet/src/test/java/org/jclouds/packet/features/FacilityApiLiveTest.java +++ b/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-labs/blob/38f0a50d/packet/src/test/resources/rescue.json ---------------------------------------------------------------------- diff --git a/packet/src/test/resources/rescue.json b/packet/src/test/resources/rescue.json deleted file mode 100644 index 91ea877..0000000 --- a/packet/src/test/resources/rescue.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "type": "rescue" -}
