Promote DigitalOcean v2

This commit is contained in:
Ignasi Barrera 2016-01-20 23:57:03 +01:00
commit 886aa156b4
119 changed files with 10612 additions and 0 deletions

View File

@ -0,0 +1,152 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.jclouds.labs</groupId>
<artifactId>jclouds-labs</artifactId>
<version>2.0.0-SNAPSHOT</version>
</parent>
<groupId>org.apache.jclouds.labs</groupId>
<artifactId>digitalocean2</artifactId>
<name>jclouds DigitalOcean v2 API Provider</name>
<description>jclouds provider for Digital Ocean v2 Compute API</description>
<properties>
<test.digitalocean2.endpoint>https://api.digitalocean.com/v2/</test.digitalocean2.endpoint>
<test.digitalocean2.api-version>2</test.digitalocean2.api-version>
<test.digitalocean2.identity>FIXME</test.digitalocean2.identity>
<test.digitalocean2.credential>FIXME</test.digitalocean2.credential>
<test.digitalocean2.template>osFamily=UBUNTU,os64Bit=true</test.digitalocean2.template>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.jclouds</groupId>
<artifactId>jclouds-core</artifactId>
<version>${jclouds.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jclouds.api</groupId>
<artifactId>oauth</artifactId>
<version>${jclouds.version}</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.apache.jclouds.api</groupId>
<artifactId>oauth</artifactId>
<version>${jclouds.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.jclouds</groupId>
<artifactId>jclouds-compute</artifactId>
<version>${jclouds.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jclouds</groupId>
<artifactId>jclouds-compute</artifactId>
<version>${jclouds.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.jclouds</groupId>
<artifactId>jclouds-core</artifactId>
<version>${jclouds.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.jclouds.driver</groupId>
<artifactId>jclouds-slf4j</artifactId>
<version>${jclouds.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.jclouds.driver</groupId>
<artifactId>jclouds-sshj</artifactId>
<version>${jclouds.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp</groupId>
<artifactId>mockwebserver</artifactId>
<scope>test</scope>
<exclusions>
<!-- Already provided by jclouds-sshj -->
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.auto.value</groupId>
<artifactId>auto-value</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<profiles>
<profile>
<id>live</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<executions>
<execution>
<id>integration</id>
<phase>integration-test</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<systemPropertyVariables>
<test.digitalocean2.endpoint>${test.digitalocean2.endpoint}</test.digitalocean2.endpoint>
<test.digitalocean2.api-version>${test.digitalocean2.api-version}</test.digitalocean2.api-version>
<test.digitalocean2.build-version>${test.digitalocean2.build-version}</test.digitalocean2.build-version>
<test.digitalocean2.identity>${test.digitalocean2.identity}</test.digitalocean2.identity>
<test.digitalocean2.credential>${test.digitalocean2.credential}</test.digitalocean2.credential>
<test.digitalocean2.template>${test.digitalocean2.template}</test.digitalocean2.template>
</systemPropertyVariables>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@ -0,0 +1,73 @@
/*
* 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.digitalocean2;
import java.io.Closeable;
import org.jclouds.digitalocean2.features.ActionApi;
import org.jclouds.digitalocean2.features.DropletApi;
import org.jclouds.digitalocean2.features.ImageApi;
import org.jclouds.digitalocean2.features.KeyApi;
import org.jclouds.digitalocean2.features.RegionApi;
import org.jclouds.digitalocean2.features.SizeApi;
import org.jclouds.rest.annotations.Delegate;
import com.google.common.annotations.Beta;
/**
* Provides access to DigitalOcean.
*/
@Beta
public interface DigitalOcean2Api extends Closeable {
/**
* Provides access to Droplet features
*/
@Delegate
DropletApi dropletApi();
/**
* Provides access to SSH Key features
*/
@Delegate
KeyApi keyApi();
/**
* Provides access to Images
*/
@Delegate
ImageApi imageApi();
/**
* Provides access to Actions
*/
@Delegate
ActionApi actionApi();
/**
* Provides access to Sizes
*/
@Delegate
SizeApi sizeApi();
/**
* Provides access to Regions
*/
@Delegate
RegionApi regionApi();
}

View File

@ -0,0 +1,111 @@
/*
* 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.digitalocean2;
import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
import static org.jclouds.compute.config.ComputeServiceProperties.POLL_INITIAL_PERIOD;
import static org.jclouds.compute.config.ComputeServiceProperties.POLL_MAX_PERIOD;
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.compute.config.ComputeServiceProperties.TIMEOUT_NODE_TERMINATED;
import static org.jclouds.oauth.v2.config.CredentialType.BEARER_TOKEN_CREDENTIALS;
import static org.jclouds.oauth.v2.config.OAuthProperties.AUDIENCE;
import static org.jclouds.oauth.v2.config.OAuthProperties.CREDENTIAL_TYPE;
import static org.jclouds.oauth.v2.config.OAuthProperties.JWS_ALG;
import static org.jclouds.reflect.Reflection2.typeToken;
import java.net.URI;
import java.util.Properties;
import org.jclouds.apis.ApiMetadata;
import org.jclouds.compute.ComputeServiceContext;
import org.jclouds.digitalocean2.compute.config.DigitalOcean2ComputeServiceContextModule;
import org.jclouds.digitalocean2.config.DigitalOcean2HttpApiModule;
import org.jclouds.digitalocean2.config.DigitalOceanParserModule;
import org.jclouds.oauth.v2.config.OAuthModule;
import org.jclouds.rest.internal.BaseHttpApiMetadata;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Module;
/**
* Implementation of {@link ApiMetadata} for DigitalOcean v2 API
*/
public class DigitalOcean2ApiMetadata extends BaseHttpApiMetadata<DigitalOcean2Api> {
@Override
public Builder toBuilder() {
return new Builder().fromApiMetadata(this);
}
public DigitalOcean2ApiMetadata() {
this(new Builder());
}
protected DigitalOcean2ApiMetadata(Builder builder) {
super(builder);
}
public static Properties defaultProperties() {
Properties properties = BaseHttpApiMetadata.defaultProperties();
properties.put("oauth.endpoint", "https://cloud.digitalocean.com/v1/oauth/token");
properties.put(JWS_ALG, "RS256");
properties.put(AUDIENCE, "https://cloud.digitalocean.com/v1/oauth/token");
properties.put(CREDENTIAL_TYPE, BEARER_TOKEN_CREDENTIALS.toString());
properties.put(PROPERTY_SESSION_INTERVAL, 3600);
properties.put(TEMPLATE, "osFamily=UBUNTU,os64Bit=true");
properties.put(POLL_INITIAL_PERIOD, 5000);
properties.put(POLL_MAX_PERIOD, 20000);
// Node operations in DigitalOcean can be quite slow. Use a 5 minutes
// timeout by default
properties.put(TIMEOUT_NODE_RUNNING, 300000);
properties.put(TIMEOUT_NODE_SUSPENDED, 300000);
properties.put(TIMEOUT_NODE_TERMINATED, 300000);
return properties;
}
public static class Builder extends BaseHttpApiMetadata.Builder<DigitalOcean2Api, Builder> {
protected Builder() {
id("digitalocean2")
.name("Digital Ocean v2 API")
.identityName("Not used for OAuth")
.credentialName("Must be oauth2 Bearer Token")
.documentation(URI.create("https://developers.digitalocean.com/v2/"))
.defaultEndpoint("https://api.digitalocean.com/v2")
.defaultProperties(DigitalOcean2ApiMetadata.defaultProperties())
.view(typeToken(ComputeServiceContext.class))
.defaultModules(ImmutableSet.<Class<? extends Module>>builder()
.add(DigitalOcean2HttpApiModule.class)
.add(OAuthModule.class)
.add(DigitalOceanParserModule.class)
.add(DigitalOcean2ComputeServiceContextModule.class)
.build());
}
@Override
public DigitalOcean2ApiMetadata build() {
return new DigitalOcean2ApiMetadata(this);
}
@Override
protected Builder self() {
return this;
}
}
}

View File

@ -0,0 +1,78 @@
/*
* 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.digitalocean2;
import java.net.URI;
import java.util.Properties;
import org.jclouds.providers.ProviderMetadata;
import org.jclouds.providers.internal.BaseProviderMetadata;
import com.google.auto.service.AutoService;
/**
* Implementation of {@link org.jclouds.providers.ProviderMetadata} for DigitalOcean.
*/
@AutoService(ProviderMetadata.class)
public class DigitalOcean2ProviderMetadata extends BaseProviderMetadata {
public static Builder builder() {
return new Builder();
}
@Override
public Builder toBuilder() {
return builder().fromProviderMetadata(this);
}
public DigitalOcean2ProviderMetadata() {
super(builder());
}
public DigitalOcean2ProviderMetadata(Builder builder) {
super(builder);
}
public static Properties defaultProperties() {
Properties properties = DigitalOcean2ApiMetadata.defaultProperties();
return properties;
}
public static class Builder extends BaseProviderMetadata.Builder {
protected Builder() {
id("digitalocean2")
.name("DigitalOcean Compute Services")
.apiMetadata(new DigitalOcean2ApiMetadata())
.homepage(URI.create("https://www.digitalocean.com/"))
.console(URI.create("https://cloud.digitalocean.com/"))
.endpoint("https://api.digitalocean.com/v2")
.defaultProperties(DigitalOcean2ProviderMetadata.defaultProperties());
}
@Override
public DigitalOcean2ProviderMetadata build() {
return new DigitalOcean2ProviderMetadata(this);
}
@Override
public Builder fromProviderMetadata(ProviderMetadata in) {
super.fromProviderMetadata(in);
return this;
}
}
}

View File

@ -0,0 +1,243 @@
/*
* 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.digitalocean2.compute;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Predicates.notNull;
import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Iterables.contains;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.transform;
import static com.google.common.collect.Sets.newHashSet;
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.compute.util.ComputeServiceUtils.metadataAndTagsAsCommaDelimitedValue;
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 org.jclouds.compute.ComputeServiceAdapter;
import org.jclouds.compute.domain.Template;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.digitalocean2.DigitalOcean2Api;
import org.jclouds.digitalocean2.compute.internal.ImageInRegion;
import org.jclouds.digitalocean2.compute.options.DigitalOcean2TemplateOptions;
import org.jclouds.digitalocean2.domain.Droplet;
import org.jclouds.digitalocean2.domain.DropletCreate;
import org.jclouds.digitalocean2.domain.Image;
import org.jclouds.digitalocean2.domain.Region;
import org.jclouds.digitalocean2.domain.Size;
import org.jclouds.digitalocean2.domain.options.CreateDropletOptions;
import org.jclouds.domain.LoginCredentials;
import org.jclouds.json.Json;
import org.jclouds.logging.Logger;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.primitives.Ints;
/**
* Implementation of the Compute Service for the DigitalOcean API.
*/
public class DigitalOcean2ComputeServiceAdapter implements ComputeServiceAdapter<Droplet, Size, ImageInRegion, Region> {
@Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL;
private final DigitalOcean2Api api;
private final Predicate<Integer> nodeRunningPredicate;
private final Predicate<Integer> nodeStoppedPredicate;
private final Predicate<Integer> nodeTerminatedPredicate;
private final Json json;
@Inject DigitalOcean2ComputeServiceAdapter(DigitalOcean2Api api,
@Named(TIMEOUT_NODE_RUNNING) Predicate<Integer> nodeRunningPredicate,
@Named(TIMEOUT_NODE_SUSPENDED) Predicate<Integer> nodeStoppedPredicate,
@Named(TIMEOUT_NODE_TERMINATED) Predicate<Integer> nodeTerminatedPredicate,
Json json) {
this.api = api;
this.nodeRunningPredicate = nodeRunningPredicate;
this.nodeStoppedPredicate = nodeStoppedPredicate;
this.nodeTerminatedPredicate = nodeTerminatedPredicate;
this.json = json;
}
@Override
public NodeAndInitialCredentials<Droplet> createNodeWithGroupEncodedIntoName(String group, final String name,
Template template) {
DigitalOcean2TemplateOptions templateOptions = template.getOptions().as(DigitalOcean2TemplateOptions.class);
CreateDropletOptions.Builder options = CreateDropletOptions.builder();
// DigitalOcean specific options
options.privateNetworking(templateOptions.getPrivateNetworking());
options.backupsEnabled(templateOptions.getBackupsEnabled());
if (!templateOptions.getSshKeyIds().isEmpty()) {
options.addSshKeyIds(templateOptions.getSshKeyIds());
}
Map<String, String> metadataAndTags = metadataAndTagsAsCommaDelimitedValue(templateOptions);
if (!metadataAndTags.isEmpty()) {
@SuppressWarnings("unchecked")
List<String> regionFeatures = (List<String>) template.getLocation().getMetadata().get("features");
if (regionFeatures.contains("metadata")) {
options.userData(json.toJson(metadataAndTags));
} else {
logger.debug(">> region %s does not support metadata, ignoring provided user data", template.getLocation()
.getId());
}
}
DropletCreate dropletCreated = api.dropletApi().create(name,
template.getLocation().getId(),
template.getHardware().getProviderId(),
template.getImage().getProviderId(),
options.build());
// We have to actively wait until the droplet has been provisioned until
// we can build the entire Droplet object we want to return
nodeRunningPredicate.apply(dropletCreated.droplet().id());
Droplet droplet = api.dropletApi().get(dropletCreated.droplet().id());
LoginCredentials defaultCredentials = LoginCredentials.builder().user("root")
.privateKey(templateOptions.getLoginPrivateKey()).build();
return new NodeAndInitialCredentials<Droplet>(droplet, String.valueOf(droplet.id()), defaultCredentials);
}
@Override
public Iterable<ImageInRegion> listImages() {
// Images can claim to be available in a region that is currently marked as "unavailable". We shouldn't return
// the images scoped to those regions.
final Set<String> availableRegionsIds = newHashSet(transform(listLocations(), new Function<Region, String>() {
@Override
public String apply(Region input) {
return input.slug();
}
}));
// Public images re globally available, but non-public ones can only be available in certain regions.
// For these kind of images, return one instance of an ImageInRegion for each region where the image is
// available. This way we can properly scope global and concrete images so they can be properly looked up.
return concat(filter(api.imageApi().list().concat().transform(new Function<Image, Iterable<ImageInRegion>>() {
@Override
public Iterable<ImageInRegion> apply(final Image image) {
return transform(image.regions(), new Function<String, ImageInRegion>() {
@Override
public ImageInRegion apply(String region) {
return availableRegionsIds.contains(region) ? ImageInRegion.create(image, region) : null;
}
});
}
}), notNull()));
}
@Override
public Iterable<Size> listHardwareProfiles() {
return filter(api.sizeApi().list().concat(), new Predicate<Size>() {
@Override
public boolean apply(Size size) {
return size.available();
}
});
}
@Override
public Iterable<Region> listLocations() {
// DigitalOcean lists regions that are unavailable for droplet creation
return filter(api.regionApi().list().concat(), new Predicate<Region>() {
@Override
public boolean apply(Region region) {
return region.available();
}
});
}
@Override
public Iterable<Droplet> listNodes() {
return api.dropletApi().list().concat();
}
@Override
public Iterable<Droplet> listNodesByIds(final Iterable<String> ids) {
return filter(listNodes(), new Predicate<Droplet>() {
@Override
public boolean apply(Droplet droplet) {
return contains(ids, String.valueOf(droplet.id()));
}
});
}
@Override
public ImageInRegion getImage(String id) {
String region = ImageInRegion.extractRegion(id);
String imageId = ImageInRegion.extractImageId(id);
// The id of the image can be an id or a slug. Use the corresponding method of the API depending on what is
// provided. If it can be parsed as a number, use the method to get by ID. Otherwise, get by slug.
Integer numericId = Ints.tryParse(imageId);
Image image = numericId == null ? api.imageApi().get(imageId) : api.imageApi().get(numericId);
return image == null ? null : ImageInRegion.create(image, region);
}
@Override
public Droplet getNode(String id) {
return api.dropletApi().get(Integer.parseInt(id));
}
@Override
public void destroyNode(String id) {
// We have to wait here, as the api does not properly populate the state
// but fails if there is a pending event
int dropletId = Integer.parseInt(id);
api.dropletApi().delete(dropletId);
checkState(nodeTerminatedPredicate.apply(dropletId), "node was not destroyed in the configured timeout");
}
@Override
public void rebootNode(String id) {
// We have to wait here, as the api does not properly populate the state
// but fails if there is a pending event
int dropletId = Integer.parseInt(id);
api.dropletApi().reboot(dropletId);
checkState(nodeRunningPredicate.apply(dropletId), "node did not restart in the configured timeout");
}
@Override
public void resumeNode(String id) {
// We have to wait here, as the api does not properly populate the state
// but fails if there is a pending event
int dropletId = Integer.parseInt(id);
api.dropletApi().powerOn(dropletId);
checkState(nodeRunningPredicate.apply(dropletId), "node did not started in the configured timeout");
}
@Override
public void suspendNode(String id) {
// We have to wait here, as the api does not properly populate the state
// but fails if there is a pending event
int dropletId = Integer.parseInt(id);
api.dropletApi().powerOff(dropletId);
checkState(nodeStoppedPredicate.apply(dropletId), "node did not stop in the configured timeout");
}
}

View File

@ -0,0 +1,218 @@
/*
* 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.digitalocean2.compute.config;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_IMAGE_AVAILABLE;
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;
import javax.inject.Singleton;
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.domain.NodeMetadata.Status;
import org.jclouds.compute.extensions.ImageExtension;
import org.jclouds.compute.functions.TemplateOptionsToStatement;
import org.jclouds.compute.options.TemplateOptions;
import org.jclouds.compute.reference.ComputeServiceConstants.PollPeriod;
import org.jclouds.compute.reference.ComputeServiceConstants.Timeouts;
import org.jclouds.compute.strategy.CreateNodesInGroupThenAddToSet;
import org.jclouds.digitalocean2.DigitalOcean2Api;
import org.jclouds.digitalocean2.compute.DigitalOcean2ComputeServiceAdapter;
import org.jclouds.digitalocean2.compute.extensions.DigitalOcean2ImageExtension;
import org.jclouds.digitalocean2.compute.functions.DropletStatusToStatus;
import org.jclouds.digitalocean2.compute.functions.DropletToNodeMetadata;
import org.jclouds.digitalocean2.compute.functions.ImageInRegionToImage;
import org.jclouds.digitalocean2.compute.functions.RegionToLocation;
import org.jclouds.digitalocean2.compute.functions.SizeToHardware;
import org.jclouds.digitalocean2.compute.functions.TemplateOptionsToStatementWithoutPublicKey;
import org.jclouds.digitalocean2.compute.internal.ImageInRegion;
import org.jclouds.digitalocean2.compute.options.DigitalOcean2TemplateOptions;
import org.jclouds.digitalocean2.compute.strategy.CreateKeyPairsThenCreateNodes;
import org.jclouds.digitalocean2.domain.Action;
import org.jclouds.digitalocean2.domain.Droplet;
import org.jclouds.digitalocean2.domain.Region;
import org.jclouds.digitalocean2.domain.Size;
import org.jclouds.domain.Location;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.inject.Provides;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
/**
* Configures the compute service classes for the DigitalOcean API.
*/
public class DigitalOcean2ComputeServiceContextModule extends
ComputeServiceAdapterContextModule<Droplet, Size, ImageInRegion, Region> {
@Override
protected void configure() {
super.configure();
bind(new TypeLiteral<ComputeServiceAdapter<Droplet, Size, ImageInRegion, Region>>() {
}).to(DigitalOcean2ComputeServiceAdapter.class);
bind(new TypeLiteral<Function<Droplet, NodeMetadata>>() {
}).to(DropletToNodeMetadata.class);
bind(new TypeLiteral<Function<ImageInRegion, Image>>() {
}).to(ImageInRegionToImage.class);
bind(new TypeLiteral<Function<Region, Location>>() {
}).to(RegionToLocation.class);
bind(new TypeLiteral<Function<Size, Hardware>>() {
}).to(SizeToHardware.class);
bind(new TypeLiteral<Function<Droplet.Status, Status>>() {
}).to(DropletStatusToStatus.class);
install(new LocationsFromComputeServiceAdapterModule<Droplet, Size, ImageInRegion, Region>() {
});
bind(CreateNodesInGroupThenAddToSet.class).to(CreateKeyPairsThenCreateNodes.class);
bind(TemplateOptions.class).to(DigitalOcean2TemplateOptions.class);
bind(TemplateOptionsToStatement.class).to(TemplateOptionsToStatementWithoutPublicKey.class);
bind(new TypeLiteral<ImageExtension>() {
}).to(DigitalOcean2ImageExtension.class);
}
@Provides
@Named(TIMEOUT_NODE_RUNNING)
protected Predicate<Integer> provideDropletRunningPredicate(final DigitalOcean2Api api, Timeouts timeouts,
PollPeriod pollPeriod) {
return retry(new DropletInStatusPredicate(api, Droplet.Status.ACTIVE), timeouts.nodeRunning,
pollPeriod.pollInitialPeriod, pollPeriod.pollMaxPeriod);
}
@Provides
@Named(TIMEOUT_NODE_SUSPENDED)
protected Predicate<Integer> provideDropletSuspendedPredicate(final DigitalOcean2Api api, Timeouts timeouts,
PollPeriod pollPeriod) {
return retry(new DropletInStatusPredicate(api, Droplet.Status.OFF), timeouts.nodeSuspended,
pollPeriod.pollInitialPeriod, pollPeriod.pollMaxPeriod);
}
@Provides
@Named(TIMEOUT_NODE_TERMINATED)
protected Predicate<Integer> provideDropletTerminatedPredicate(final DigitalOcean2Api api, Timeouts timeouts,
PollPeriod pollPeriod) {
return retry(new DropletTerminatedPredicate(api), timeouts.nodeTerminated, pollPeriod.pollInitialPeriod,
pollPeriod.pollMaxPeriod);
}
@Provides
@Named(TIMEOUT_IMAGE_AVAILABLE)
protected Predicate<Integer> provideImageAvailablePredicate(final DigitalOcean2Api api, Timeouts timeouts,
PollPeriod pollPeriod) {
return retry(new ActionDonePredicate(api), timeouts.imageAvailable, pollPeriod.pollInitialPeriod,
pollPeriod.pollMaxPeriod);
}
@Provides
@Singleton
protected Predicate<Region> provideRegionAvailablePredicate(final DigitalOcean2Api api, Timeouts timeouts,
PollPeriod pollPeriod) {
return retry(new RegionAvailablePredicate(), timeouts.imageAvailable, pollPeriod.pollInitialPeriod,
pollPeriod.pollMaxPeriod);
}
@Provides
protected Predicate<Integer> provideActionCompletedPredicate(final DigitalOcean2Api api, Timeouts timeouts,
PollPeriod pollPeriod) {
return retry(new ActionDonePredicate(api), timeouts.imageAvailable, pollPeriod.pollInitialPeriod,
pollPeriod.pollMaxPeriod);
}
@VisibleForTesting
static class ActionDonePredicate implements Predicate<Integer> {
private final DigitalOcean2Api api;
public ActionDonePredicate(DigitalOcean2Api api) {
this.api = checkNotNull(api, "api must not be null");
}
@Override
public boolean apply(Integer input) {
checkNotNull(input, "action id cannot be null");
Action current = api.actionApi().get(input);
switch (current.status()) {
case COMPLETED:
return true;
case IN_PROGRESS:
return false;
case ERRORED:
default:
throw new IllegalStateException("Resource is in invalid status: " + current.status().name());
}
}
}
@VisibleForTesting
static class DropletTerminatedPredicate implements Predicate<Integer> {
private final DigitalOcean2Api api;
public DropletTerminatedPredicate(DigitalOcean2Api api) {
this.api = checkNotNull(api, "api must not be null");
}
@Override
public boolean apply(Integer input) {
checkNotNull(input, "droplet id");
Droplet droplet = api.dropletApi().get(input);
return droplet == null;
}
}
@VisibleForTesting
static class DropletInStatusPredicate implements Predicate<Integer> {
private final DigitalOcean2Api api;
private final Droplet.Status status;
public DropletInStatusPredicate(DigitalOcean2Api api, Droplet.Status status) {
this.api = checkNotNull(api, "api must not be null");
this.status = checkNotNull(status, "status must not be null");
}
@Override
public boolean apply(Integer input) {
checkNotNull(input, "droplet id");
Droplet droplet = api.dropletApi().get(input);
return droplet != null && status == droplet.status();
}
}
@VisibleForTesting
static class RegionAvailablePredicate implements Predicate<Region> {
@Override
public boolean apply(Region input) {
return input.available();
}
}
}

View File

@ -0,0 +1,149 @@
/*
* 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.digitalocean2.compute.extensions;
import static com.google.common.base.Preconditions.checkState;
import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_IMAGE_AVAILABLE;
import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED;
import java.util.NoSuchElementException;
import java.util.concurrent.Callable;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.jclouds.Constants;
import org.jclouds.compute.domain.CloneImageTemplate;
import org.jclouds.compute.domain.Image;
import org.jclouds.compute.domain.ImageTemplate;
import org.jclouds.compute.domain.ImageTemplateBuilder;
import org.jclouds.compute.extensions.ImageExtension;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.digitalocean2.DigitalOcean2Api;
import org.jclouds.digitalocean2.compute.internal.ImageInRegion;
import org.jclouds.digitalocean2.domain.Action;
import org.jclouds.digitalocean2.domain.Droplet;
import org.jclouds.digitalocean2.domain.Droplet.Status;
import org.jclouds.logging.Logger;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.primitives.Ints;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.UncheckedTimeoutException;
/**
* The {@link org.jclouds.compute.extensions.ImageExtension} implementation for the DigitalOcean provider.
*/
@Singleton
public class DigitalOcean2ImageExtension implements ImageExtension {
@Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL;
private final DigitalOcean2Api api;
private final Predicate<Integer> imageAvailablePredicate;
private final Predicate<Integer> nodeStoppedPredicate;
private final Function<ImageInRegion, Image> imageTransformer;
private final ListeningExecutorService userExecutor;
@Inject DigitalOcean2ImageExtension(DigitalOcean2Api api,
@Named(TIMEOUT_IMAGE_AVAILABLE) Predicate<Integer> imageAvailablePredicate,
@Named(TIMEOUT_NODE_SUSPENDED) Predicate<Integer> nodeStoppedPredicate,
Function<ImageInRegion, Image> imageTransformer,
@Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor) {
this.api = api;
this.imageAvailablePredicate = imageAvailablePredicate;
this.nodeStoppedPredicate = nodeStoppedPredicate;
this.imageTransformer = imageTransformer;
this.userExecutor = userExecutor;
}
@Override
public ImageTemplate buildImageTemplateFromNode(String name, String id) {
Droplet droplet = api.dropletApi().get(Integer.parseInt(id));
if (droplet == null) {
throw new NoSuchElementException("Cannot find droplet with id: " + id);
}
return new ImageTemplateBuilder.CloneImageTemplateBuilder().nodeId(id).name(name).build();
}
@Override
public ListenableFuture<Image> createImage(ImageTemplate template) {
checkState(template instanceof CloneImageTemplate, "DigitalOcean only supports creating images through cloning.");
final CloneImageTemplate cloneTemplate = (CloneImageTemplate) template;
int dropletId = Integer.parseInt(cloneTemplate.getSourceNodeId());
// Droplet needs to be stopped
final Droplet droplet = api.dropletApi().get(dropletId);
if (droplet.status() != Status.OFF) {
api.dropletApi().powerOff(dropletId);
checkState(nodeStoppedPredicate.apply(dropletId), "node was not powered off in the configured timeout");
}
final Action snapshotEvent = api.dropletApi().snapshot(Integer.parseInt(cloneTemplate.getSourceNodeId()),
cloneTemplate.getName());
logger.info(">> registered new Image, waiting for it to become available");
return userExecutor.submit(new Callable<Image>() {
@Override
public Image call() throws Exception {
if (imageAvailablePredicate.apply(snapshotEvent.id())) {
org.jclouds.digitalocean2.domain.Image snapshot = api.imageApi().list().concat()
.firstMatch(new Predicate<org.jclouds.digitalocean2.domain.Image>() {
@Override
public boolean apply(org.jclouds.digitalocean2.domain.Image input) {
return input.name().equals(cloneTemplate.getName());
}
}).get();
return imageTransformer.apply(ImageInRegion.create(snapshot, droplet.region().slug()));
}
throw new UncheckedTimeoutException("Image was not created within the time limit: "
+ cloneTemplate.getName());
}
});
}
@Override
public boolean deleteImage(String id) {
String imageId = ImageInRegion.extractImageId(id);
Integer numericId = Ints.tryParse(imageId); // User images don't have a slug, so we expect a numeric id here
if (numericId != null) {
try {
logger.debug(">> deleting image %s...", id);
api.imageApi().delete(numericId);
return true;
} catch (Exception ex) {
logger.error(ex, ">> error deleting image %s", id);
}
} else {
logger.warn(">> image %s is not a user image and cannot be deleted", id);
}
return false;
}
}

View File

@ -0,0 +1,46 @@
/*
* 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.digitalocean2.compute.functions;
import javax.inject.Singleton;
import org.jclouds.compute.domain.NodeMetadata.Status;
import org.jclouds.digitalocean2.domain.Droplet;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.collect.ImmutableMap;
/**
* Transforms an {@link org.jclouds.compute.domain.NodeMetadata.Status} to the jclouds portable model.
*/
@Singleton
public class DropletStatusToStatus implements Function<Droplet.Status, Status> {
private static final Function<Droplet.Status, Status> toPortableStatus = Functions.forMap(
ImmutableMap.<Droplet.Status, Status> builder()
.put(Droplet.Status.NEW, Status.PENDING)
.put(Droplet.Status.ACTIVE, Status.RUNNING)
.put(Droplet.Status.ARCHIVE, Status.TERMINATED)
.put(Droplet.Status.OFF, Status.SUSPENDED)
.build(),
Status.UNRECOGNIZED);
@Override
public Status apply(final Droplet.Status input) {
return toPortableStatus.apply(input);
}
}

View File

@ -0,0 +1,165 @@
/*
* 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.digitalocean2.compute.functions;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Iterables.find;
import static org.jclouds.digitalocean2.compute.internal.ImageInRegion.encodeId;
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.collect.Memoized;
import org.jclouds.compute.domain.Hardware;
import org.jclouds.compute.domain.Image;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.domain.NodeMetadata.Status;
import org.jclouds.compute.domain.NodeMetadataBuilder;
import org.jclouds.compute.functions.GroupNamingConvention;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.digitalocean2.compute.internal.ImageInRegion;
import org.jclouds.digitalocean2.domain.Droplet;
import org.jclouds.digitalocean2.domain.Networks;
import org.jclouds.digitalocean2.domain.Region;
import org.jclouds.domain.Credentials;
import org.jclouds.domain.Location;
import org.jclouds.domain.LoginCredentials;
import org.jclouds.logging.Logger;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Iterables;
/**
* Transforms an {@link Droplet} to the jclouds portable model.
*/
@Singleton
public class DropletToNodeMetadata implements Function<Droplet, NodeMetadata> {
@Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL;
private final Supplier<Map<String, ? extends Image>> images;
private final Supplier<Map<String, ? extends Hardware>> hardwares;
private final Supplier<Set<? extends Location>> locations;
private final Function<Droplet.Status, Status> toPortableStatus;
private final GroupNamingConvention groupNamingConvention;
private final Map<String, Credentials> credentialStore;
@Inject
DropletToNodeMetadata(Supplier<Map<String, ? extends Image>> images,
Supplier<Map<String, ? extends Hardware>> hardwares, @Memoized Supplier<Set<? extends Location>> locations,
Function<Droplet.Status, Status> toPortableStatus, GroupNamingConvention.Factory groupNamingConvention,
Map<String, Credentials> credentialStore) {
this.images = checkNotNull(images, "images cannot be null");
this.hardwares = checkNotNull(hardwares, "hardwares cannot be null");
this.locations = checkNotNull(locations, "locations cannot be null");
this.toPortableStatus = checkNotNull(toPortableStatus, "toPortableStatus cannot be null");
this.groupNamingConvention = checkNotNull(groupNamingConvention, "groupNamingConvention cannot be null")
.createWithoutPrefix();
this.credentialStore = checkNotNull(credentialStore, "credentialStore cannot be null");
}
@Override
public NodeMetadata apply(Droplet input) {
NodeMetadataBuilder builder = new NodeMetadataBuilder();
builder.ids(String.valueOf(input.id()));
builder.name(input.name());
builder.hostname(input.name());
builder.group(groupNamingConvention.extractGroup(input.name()));
builder.hardware(getHardware(input.sizeSlug()));
builder.location(getLocation(input.region()));
Optional<? extends Image> image = findImage(input.image(), input.region().slug());
if (image.isPresent()) {
builder.imageId(image.get().getId());
builder.operatingSystem(image.get().getOperatingSystem());
} else {
logger.info(">> image with id %s for droplet %s was not found. "
+ "This might be because the image that was used to create the droplet has a new id.",
input.image().id(), input.id());
}
builder.status(toPortableStatus.apply(input.status()));
builder.backendStatus(input.status().name());
if (!input.getPublicAddresses().isEmpty()) {
builder.publicAddresses(FluentIterable
.from(input.getPublicAddresses())
.transform(new Function<Networks.Address, String>() {
@Override
public String apply(final Networks.Address input) {
return input.ip();
}
})
);
}
if (!input.getPrivateAddresses().isEmpty()) {
builder.privateAddresses(FluentIterable
.from(input.getPrivateAddresses())
.transform(new Function<Networks.Address, String>() {
@Override
public String apply(final Networks.Address input) {
return input.ip();
}
})
);
}
// DigitalOcean does not provide a way to get the credentials.
// Try to return them from the credential store
Credentials credentials = credentialStore.get("node#" + input.id());
if (credentials instanceof LoginCredentials) {
builder.credentials(LoginCredentials.class.cast(credentials));
}
return builder.build();
}
protected Optional<? extends Image> findImage(org.jclouds.digitalocean2.domain.Image image, String region) {
return Optional.fromNullable(images.get().get(encodeId(ImageInRegion.create(image, region))));
}
protected Hardware getHardware(final String slug) {
return Iterables.find(hardwares.get().values(), new Predicate<Hardware>() {
@Override
public boolean apply(Hardware input) {
return input.getId().equals(slug);
}
});
}
protected Location getLocation(final Region region) {
return find(locations.get(), new Predicate<Location>() {
@Override
public boolean apply(Location location) {
return region != null && region.slug().equals(location.getId());
}
}, null);
}
}

View File

@ -0,0 +1,92 @@
/*
* 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.digitalocean2.compute.functions;
import static com.google.common.collect.Iterables.find;
import static org.jclouds.compute.domain.OperatingSystem.builder;
import static org.jclouds.digitalocean2.compute.internal.ImageInRegion.encodeId;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.jclouds.collect.Memoized;
import org.jclouds.compute.domain.Image;
import org.jclouds.compute.domain.Image.Status;
import org.jclouds.compute.domain.ImageBuilder;
import org.jclouds.digitalocean2.compute.internal.ImageInRegion;
import org.jclouds.digitalocean2.domain.OperatingSystem;
import org.jclouds.domain.Location;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableMap;
/**
* Transforms an {@link ImageInRegion} to the jclouds portable model.
*/
@Singleton
public class ImageInRegionToImage implements Function<ImageInRegion, Image> {
private final Supplier<Set<? extends Location>> locations;
@Inject ImageInRegionToImage(@Memoized Supplier<Set<? extends Location>> locations) {
this.locations = locations;
}
@Override
public Image apply(final ImageInRegion input) {
String description = input.image().distribution() + " " + input.image().name();
ImageBuilder builder = new ImageBuilder();
// Private images don't have a slug
builder.id(encodeId(input));
builder.providerId(String.valueOf(input.image().id()));
builder.name(input.image().name());
builder.description(description);
builder.status(Status.AVAILABLE);
builder.location(getLocation(input.region()));
OperatingSystem os = OperatingSystem.create(input.image().name(), input.image().distribution());
builder.operatingSystem(builder()
.name(os.distribution().value())
.family(os.distribution().osFamily())
.description(description)
.arch(os.arch())
.version(os.version())
.is64Bit(os.is64bit())
.build());
ImmutableMap.Builder<String, String> metadata = ImmutableMap.builder();
metadata.put("publicImage", String.valueOf(input.image().isPublic()));
builder.userMetadata(metadata.build());
return builder.build();
}
protected Location getLocation(final String region) {
return find(locations.get(), new Predicate<Location>() {
@Override
public boolean apply(Location location) {
return region.equals(location.getId());
}
});
}
}

View File

@ -0,0 +1,59 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.digitalocean2.compute.functions;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Iterables.getOnlyElement;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.jclouds.digitalocean2.domain.Region;
import org.jclouds.domain.Location;
import org.jclouds.domain.LocationBuilder;
import org.jclouds.domain.LocationScope;
import org.jclouds.location.suppliers.all.JustProvider;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
/**
* Transforms an {@link Region} to the jclouds portable model.
*/
@Singleton
public class RegionToLocation implements Function<Region, Location> {
private final JustProvider justProvider;
@Inject
RegionToLocation(JustProvider justProvider) {
this.justProvider = checkNotNull(justProvider, "justProvider cannot be null");
}
@Override
public Location apply(Region input) {
LocationBuilder builder = new LocationBuilder();
builder.id(input.slug());
builder.description(input.name());
builder.scope(LocationScope.REGION);
builder.parent(getOnlyElement(justProvider.get()));
builder.iso3166Codes(ImmutableSet.<String> of());
builder.metadata(ImmutableMap.<String, Object> of("available", input.available(), "features", input.features()));
return builder.build();
}
}

View File

@ -0,0 +1,58 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.digitalocean2.compute.functions;
import javax.inject.Singleton;
import org.jclouds.compute.domain.Hardware;
import org.jclouds.compute.domain.HardwareBuilder;
import org.jclouds.compute.domain.Processor;
import org.jclouds.compute.domain.Volume.Type;
import org.jclouds.compute.domain.VolumeBuilder;
import org.jclouds.digitalocean2.domain.Size;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableMap;
/**
* Transforms an {@link Size} to the jclouds portable model.
*/
@Singleton
public class SizeToHardware implements Function<Size, Hardware> {
@Override
public Hardware apply(Size input) {
HardwareBuilder builder = new HardwareBuilder();
builder.id(input.slug());
builder.providerId(input.slug());
builder.name(input.slug());
builder.ram(input.memory());
// No cpu speed from DigitalOcean API, so assume more cores == faster
builder.processor(new Processor(input.vcpus(), input.vcpus()));
builder.volume(new VolumeBuilder()
.size(Float.valueOf(input.disk()))
.type(Type.LOCAL)
.build());
ImmutableMap.Builder<String, String> metadata = ImmutableMap.builder();
metadata.put("costPerHour", String.valueOf(input.priceHourly()));
metadata.put("costPerMonth", String.valueOf(input.priceMonthly()));
builder.userMetadata(metadata.build());
return builder.build();
}
}

View File

@ -0,0 +1,59 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.digitalocean2.compute.functions;
import javax.inject.Singleton;
import org.jclouds.compute.functions.TemplateOptionsToStatement;
import org.jclouds.compute.options.TemplateOptions;
import org.jclouds.scriptbuilder.InitScript;
import org.jclouds.scriptbuilder.domain.Statement;
import org.jclouds.scriptbuilder.domain.StatementList;
import org.jclouds.scriptbuilder.statements.ssh.InstallRSAPrivateKey;
import com.google.common.collect.ImmutableList;
/**
* Convert the template options into a statement, but ignoring the public key.
* <p>
* The {@link org.jclouds.DigitalOcean2ComputeServiceAdapter.compute.strategy.DigitalOceanComputeServiceAdapter} already takes care of
* installing it using the {@link org.jclouds.digitalocean.features.KeyPairApi}.
*/
@Singleton
public class TemplateOptionsToStatementWithoutPublicKey extends TemplateOptionsToStatement {
@Override
public Statement apply(TemplateOptions options) {
ImmutableList.Builder<Statement> builder = ImmutableList.builder();
if (options.getRunScript() != null) {
builder.add(options.getRunScript());
}
if (options.getPrivateKey() != null) {
builder.add(new InstallRSAPrivateKey(options.getPrivateKey()));
}
ImmutableList<Statement> bootstrap = builder.build();
if (!bootstrap.isEmpty()) {
if (options.getTaskName() == null && !(options.getRunScript() instanceof InitScript)) {
options.nameTask("bootstrap");
}
return bootstrap.size() == 1 ? bootstrap.get(0) : new StatementList(bootstrap);
}
return null;
}
}

View File

@ -0,0 +1,54 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.digitalocean2.compute.internal;
import org.jclouds.digitalocean2.domain.Image;
import com.google.auto.value.AutoValue;
/**
* Scopes an image to a particular region.
*/
@AutoValue
public abstract class ImageInRegion {
public abstract Image image();
public abstract String region();
public static ImageInRegion create(Image image, String region) {
return new AutoValue_ImageInRegion(image, region);
}
public static String encodeId(ImageInRegion imageInRegion) {
// Private images don't have a slug
return String.format("%s/%s", imageInRegion.region(), slugOrId(imageInRegion.image()));
}
public static String extractRegion(String imageId) {
return imageId.substring(0, imageId.indexOf('/'));
}
public static String extractImageId(String imageId) {
return imageId.substring(imageId.indexOf('/') + 1);
}
private static String slugOrId(Image image) {
return image.slug() != null ? image.slug() : String.valueOf(image.id());
}
ImageInRegion() { }
}

View File

@ -0,0 +1,174 @@
/*
* 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.digitalocean2.compute.options;
import static com.google.common.base.Objects.equal;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Set;
import org.jclouds.compute.options.TemplateOptions;
import com.google.common.base.Objects.ToStringHelper;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableSet;
/**
* Custom options for the DigitalOcean API.
*/
public class DigitalOcean2TemplateOptions extends TemplateOptions implements Cloneable {
private Set<Integer> sshKeyIds = ImmutableSet.of();
private boolean privateNetworking = false;
private boolean backupsEnabled = false;
private boolean autoCreateKeyPair = true;
/**
* Enables a private network interface if the region supports private networking.
*/
public DigitalOcean2TemplateOptions privateNetworking(boolean privateNetworking) {
this.privateNetworking = privateNetworking;
return this;
}
/**
* Enabled backups for the droplet.
*/
public DigitalOcean2TemplateOptions backupsEnabled(boolean backupsEnabled) {
this.backupsEnabled = backupsEnabled;
return this;
}
/**
* Sets the ssh key ids to be added to the droplet.
*/
public DigitalOcean2TemplateOptions sshKeyIds(Iterable<Integer> sshKeyIds) {
this.sshKeyIds = ImmutableSet.copyOf(checkNotNull(sshKeyIds, "sshKeyIds cannot be null"));
return this;
}
/**
* Sets whether an SSH key pair should be created automatically.
*/
public DigitalOcean2TemplateOptions autoCreateKeyPair(boolean autoCreateKeyPair) {
this.autoCreateKeyPair = autoCreateKeyPair;
return this;
}
public Set<Integer> getSshKeyIds() {
return sshKeyIds;
}
public boolean getPrivateNetworking() {
return privateNetworking;
}
public boolean getBackupsEnabled() {
return backupsEnabled;
}
public boolean getAutoCreateKeyPair() {
return autoCreateKeyPair;
}
@Override
public DigitalOcean2TemplateOptions clone() {
DigitalOcean2TemplateOptions options = new DigitalOcean2TemplateOptions();
copyTo(options);
return options;
}
@Override
public void copyTo(TemplateOptions to) {
super.copyTo(to);
if (to instanceof DigitalOcean2TemplateOptions) {
DigitalOcean2TemplateOptions eTo = DigitalOcean2TemplateOptions.class.cast(to);
eTo.privateNetworking(privateNetworking);
eTo.backupsEnabled(backupsEnabled);
eTo.autoCreateKeyPair(autoCreateKeyPair);
eTo.sshKeyIds(sshKeyIds);
}
}
@Override
public int hashCode() {
return Objects.hashCode(super.hashCode(), backupsEnabled, privateNetworking, autoCreateKeyPair, sshKeyIds);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
DigitalOcean2TemplateOptions other = (DigitalOcean2TemplateOptions) obj;
return super.equals(other) && equal(this.backupsEnabled, other.backupsEnabled)
&& equal(this.privateNetworking, other.privateNetworking)
&& equal(this.autoCreateKeyPair, other.autoCreateKeyPair) && equal(this.sshKeyIds, other.sshKeyIds);
}
@Override
public ToStringHelper string() {
ToStringHelper toString = super.string().omitNullValues();
toString.add("privateNetworking", privateNetworking);
toString.add("backupsEnabled", backupsEnabled);
if (!sshKeyIds.isEmpty()) {
toString.add("sshKeyIds", sshKeyIds);
}
toString.add("autoCreateKeyPair", autoCreateKeyPair);
return toString;
}
public static class Builder {
/**
* @see DigitalOcean2TemplateOptions#privateNetworking
*/
public static DigitalOcean2TemplateOptions privateNetworking(boolean privateNetworking) {
DigitalOcean2TemplateOptions options = new DigitalOcean2TemplateOptions();
return options.privateNetworking(privateNetworking);
}
/**
* @see DigitalOcean2TemplateOptions#backupsEnabled
*/
public static DigitalOcean2TemplateOptions backupsEnabled(boolean backupsEnabled) {
DigitalOcean2TemplateOptions options = new DigitalOcean2TemplateOptions();
return options.backupsEnabled(backupsEnabled);
}
/**
* @see DigitalOcean2TemplateOptions#sshKeyIds
*/
public static DigitalOcean2TemplateOptions sshKeyIds(Iterable<Integer> sshKeyIds) {
DigitalOcean2TemplateOptions options = new DigitalOcean2TemplateOptions();
return options.sshKeyIds(sshKeyIds);
}
/**
* @see DigitalOcean2TemplateOptions#autoCreateKeyPair
*/
public static DigitalOcean2TemplateOptions autoCreateKeyPair(boolean autoCreateKeyPair) {
DigitalOcean2TemplateOptions options = new DigitalOcean2TemplateOptions();
return options.autoCreateKeyPair(autoCreateKeyPair);
}
}
}

View File

@ -0,0 +1,216 @@
/*
* 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.digitalocean2.compute.strategy;
import static com.google.common.base.Preconditions.checkNotNull;
import java.security.PublicKey;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPublicKey;
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.digitalocean2.DigitalOcean2Api;
import org.jclouds.digitalocean2.compute.options.DigitalOcean2TemplateOptions;
import org.jclouds.digitalocean2.domain.Key;
import org.jclouds.digitalocean2.ssh.DSAKeys;
import org.jclouds.digitalocean2.ssh.ECDSAKeys;
import org.jclouds.logging.Logger;
import org.jclouds.ssh.SshKeyPairGenerator;
import org.jclouds.ssh.SshKeys;
import com.google.common.base.Function;
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;
@Singleton
public class CreateKeyPairsThenCreateNodes extends CreateNodesWithGroupEncodedIntoNameThenAddToSet {
@Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL;
private final DigitalOcean2Api api;
private final SshKeyPairGenerator keyGenerator;
private final Function<String, PublicKey> sshKeyToPublicKey;
@Inject
protected CreateKeyPairsThenCreateNodes(
CreateNodeWithGroupEncodedIntoName addNodeWithGroupStrategy,
ListNodesStrategy listNodesStrategy,
GroupNamingConvention.Factory namingConvention,
@Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor,
CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.Factory customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory,
DigitalOcean2Api api, SshKeyPairGenerator keyGenerator, Function<String, PublicKey> sshKeyToPublicKey) {
super(addNodeWithGroupStrategy, listNodesStrategy, namingConvention, userExecutor,
customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory);
this.api = checkNotNull(api, "api cannot be null");
this.keyGenerator = checkNotNull(keyGenerator, "keyGenerator cannot be null");
checkNotNull(userExecutor, "userExecutor cannot be null");
this.sshKeyToPublicKey = checkNotNull(sshKeyToPublicKey, "sshKeyToPublicKey cannot be null");
}
@Override
public Map<?, ListenableFuture<Void>> execute(String group, int count, Template template,
Set<NodeMetadata> goodNodes, Map<NodeMetadata, Exception> badNodes,
Multimap<NodeMetadata, CustomizationResponse> customizationResponses) {
DigitalOcean2TemplateOptions options = template.getOptions().as(DigitalOcean2TemplateOptions.class);
Set<Integer> generatedSshKeyIds = Sets.newHashSet();
// If no key has been configured and the auto-create option is set, then generate a key pair
if (options.getSshKeyIds().isEmpty() && options.getAutoCreateKeyPair()
&& 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);
}
// Set all keys (the provided and the auto-generated) in the options object so the
// DigitalOceanComputeServiceAdapter adds them all
options.sshKeyIds(Sets.union(generatedSshKeyIds, options.getSshKeyIds()));
Map<?, ListenableFuture<Void>> responses = super.execute(group, count, template, goodNodes, badNodes,
customizationResponses);
// Key pairs in DigitalOcean are only required to create the Droplets. 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(DigitalOcean2TemplateOptions options,
Set<Integer> generatedSshKeyIds) {
logger.debug(">> checking if the key pair already exists...");
PublicKey userKey = sshKeyToPublicKey.apply(options.getPublicKey());
String userFingerprint = computeFingerprint(userKey);
Key key = api.keyApi().get(userFingerprint);
if (key == null) {
logger.debug(">> key pair not found. creating a new one...");
Key newKey = api.keyApi().create(userFingerprint, options.getPublicKey());
generatedSshKeyIds.add(newKey.id());
logger.debug(">> key pair created! %s", newKey);
} else {
logger.debug(">> key pair found! %s", key);
generatedSshKeyIds.add(key.id());
}
}
private void generateKeyPairAndAddKeyToSet(DigitalOcean2TemplateOptions options, Set<Integer> generatedSshKeyIds, String prefix) {
logger.debug(">> creating default keypair for node...");
Map<String, String> defaultKeys = keyGenerator.get();
Key defaultKey = api.keyApi().create(prefix + "-" + System.getProperty("user.name"), defaultKeys.get("public"));
generatedSshKeyIds.add(defaultKey.id());
logger.debug(">> keypair created! %s", defaultKey);
// If a private key has not been explicitly set, configure the auto-generated one
if (Strings.isNullOrEmpty(options.getLoginPrivateKey())) {
options.overrideLoginPrivateKey(defaultKeys.get("private"));
}
}
private void registerAutoGeneratedKeyPairCleanupCallbacks(Map<?, ListenableFuture<Void>> responses,
final Set<Integer> 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<Integer> generatedSshKeyIds) {
logger.debug(">> cleaning up auto-generated key pairs...");
for (Integer sshKeyId : generatedSshKeyIds) {
try {
api.keyApi().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 if (key instanceof DSAPublicKey) {
DSAPublicKey dsaKey = (DSAPublicKey) key;
return DSAKeys.fingerprint(dsaKey.getParams().getP(), dsaKey.getParams().getQ(), dsaKey.getParams().getG(),
dsaKey.getY());
} else if (key instanceof ECPublicKey) {
ECPublicKey ecdsaKey = (ECPublicKey) key;
return ECDSAKeys.fingerprint(ecdsaKey);
} else {
throw new IllegalArgumentException("Only RSA and DSA keys are supported");
}
}
}

View File

@ -0,0 +1,57 @@
/*
* 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.digitalocean2.config;
import java.net.URI;
import org.jclouds.digitalocean2.DigitalOcean2Api;
import org.jclouds.digitalocean2.domain.options.ImageListOptions;
import org.jclouds.digitalocean2.domain.options.ListOptions;
import org.jclouds.digitalocean2.functions.LinkToImageListOptions;
import org.jclouds.digitalocean2.functions.LinkToListOptions;
import org.jclouds.digitalocean2.handlers.DigitalOcean2ErrorHandler;
import org.jclouds.http.HttpErrorHandler;
import org.jclouds.http.annotation.ClientError;
import org.jclouds.http.annotation.Redirection;
import org.jclouds.http.annotation.ServerError;
import org.jclouds.oauth.v2.config.OAuthScopes;
import org.jclouds.rest.ConfiguresHttpApi;
import org.jclouds.rest.config.HttpApiModule;
import com.google.common.base.Function;
import com.google.inject.TypeLiteral;
@ConfiguresHttpApi
public class DigitalOcean2HttpApiModule extends HttpApiModule<DigitalOcean2Api> {
@Override
protected void bindErrorHandlers() {
bind(HttpErrorHandler.class).annotatedWith(Redirection.class).to(DigitalOcean2ErrorHandler.class);
bind(HttpErrorHandler.class).annotatedWith(ClientError.class).to(DigitalOcean2ErrorHandler.class);
bind(HttpErrorHandler.class).annotatedWith(ServerError.class).to(DigitalOcean2ErrorHandler.class);
}
@Override
protected void configure() {
super.configure();
bind(OAuthScopes.class).toInstance(OAuthScopes.ReadOrWriteScopes.create("read", "read write"));
bind(new TypeLiteral<Function<URI, ListOptions>>() {
}).to(LinkToListOptions.class);
bind(new TypeLiteral<Function<URI, ImageListOptions>>() {
}).to(LinkToImageListOptions.class);
}
}

View File

@ -0,0 +1,33 @@
/*
* 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.digitalocean2.config;
public final class DigitalOcean2Properties {
/**
* Maximum amount of time (in milliseconds) a request will wait until retrying if
* the rate limit is exhausted.
* <p>
* Default value: 2 minutes.
*/
public static final String MAX_RATE_LIMIT_WAIT = "jclouds.max-ratelimit-wait";
private DigitalOcean2Properties() {
throw new AssertionError("intentionally unimplemented");
}
}

View File

@ -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.digitalocean2.config;
import org.jclouds.digitalocean2.handlers.RateLimitRetryHandler;
import org.jclouds.http.HttpRetryHandler;
import org.jclouds.http.annotation.ClientError;
import com.google.inject.AbstractModule;
public class DigitalOcean2RateLimitModule extends AbstractModule {
@Override
protected void configure() {
bind(HttpRetryHandler.class).annotatedWith(ClientError.class).to(RateLimitRetryHandler.class);
}
}

View File

@ -0,0 +1,144 @@
/*
* 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.digitalocean2.config;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Throwables.propagate;
import static com.google.common.collect.Iterables.get;
import static com.google.common.collect.Iterables.size;
import static com.google.inject.Scopes.SINGLETON;
import java.io.IOException;
import java.lang.reflect.Type;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.DSAPublicKeySpec;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.jclouds.digitalocean2.ssh.DSAKeys;
import org.jclouds.digitalocean2.ssh.ECDSAKeys;
import org.jclouds.json.config.GsonModule.DateAdapter;
import org.jclouds.json.config.GsonModule.Iso8601DateAdapter;
import org.jclouds.ssh.SshKeys;
import com.google.common.base.Function;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
/**
* Custom parser bindings.
*/
public class DigitalOceanParserModule extends AbstractModule {
@Override
protected void configure() {
bind(DateAdapter.class).to(Iso8601DateAdapter.class).in(SINGLETON);
}
@Singleton
public static class SshPublicKeyAdapter extends TypeAdapter<PublicKey> {
private final Function<PublicKey, String> publicKeyToSshKey;
private final Function<String, PublicKey> sshKeyToPublicKey;
@Inject
public SshPublicKeyAdapter(Function<PublicKey, String> publicKeyToSshKey,
Function<String, PublicKey> sshKeyToPublicKey) {
this.publicKeyToSshKey = checkNotNull(publicKeyToSshKey, "publicKeyToSshKey cannot be null");
this.sshKeyToPublicKey = checkNotNull(sshKeyToPublicKey, "sshKeyToPublicKey cannot be null");
}
@Override
public void write(JsonWriter out, PublicKey value) throws IOException {
out.value(publicKeyToSshKey.apply(value));
}
@Override
public PublicKey read(JsonReader in) throws IOException {
return sshKeyToPublicKey.apply(in.nextString().trim());
}
}
@Provides
@Singleton
public Function<PublicKey, String> publicKeyToSshKey() {
return new Function<PublicKey, String>() {
@Override
public String apply(PublicKey input) {
if (input instanceof RSAPublicKey) {
return SshKeys.encodeAsOpenSSH((RSAPublicKey) input);
} else if (input instanceof DSAPublicKey) {
return DSAKeys.encodeAsOpenSSH((DSAPublicKey) input);
} else {
throw new IllegalArgumentException("Only RSA and DSA keys are supported");
}
}
};
}
@Provides
@Singleton
public Function<String, PublicKey> sshKeyToPublicKey() {
return new Function<String, PublicKey>() {
@Override
public PublicKey apply(String input) {
Iterable<String> parts = Splitter.on(' ').split(input);
checkArgument(size(parts) >= 2, "bad format, should be: [ssh-rsa|ssh-dss] AAAAB3...");
String type = get(parts, 0);
try {
if ("ssh-rsa".equals(type)) {
RSAPublicKeySpec spec = SshKeys.publicKeySpecFromOpenSSH(input);
return KeyFactory.getInstance("RSA").generatePublic(spec);
} else if ("ssh-dss".equals(type)) {
DSAPublicKeySpec spec = DSAKeys.publicKeySpecFromOpenSSH(input);
return KeyFactory.getInstance("DSA").generatePublic(spec);
} else if (type.startsWith("ecdsa-sha2-")) {
ECPublicKeySpec spec = ECDSAKeys.publicKeySpecFromOpenSSH(input);
return KeyFactory.getInstance("EC").generatePublic(spec);
} else {
throw new IllegalArgumentException("bad format, jclouds supports ssh-rsa, ssh-dss, ecdsa-sha2-nistp[256|384|521]");
}
} catch (InvalidKeySpecException ex) {
throw propagate(ex);
} catch (NoSuchAlgorithmException ex) {
throw propagate(ex);
}
}
};
}
@Provides
@Singleton
public Map<Type, Object> provideCustomAdapterBindings(SshPublicKeyAdapter sshPublicKeyAdapter) {
return ImmutableMap.<Type, Object> of(PublicKey.class, sshPublicKeyAdapter);
}
}

View File

@ -0,0 +1,71 @@
/*
* 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.digitalocean2.domain;
import static com.google.common.base.Preconditions.checkArgument;
import java.util.Date;
import org.jclouds.javax.annotation.Nullable;
import org.jclouds.json.SerializedNames;
import com.google.auto.value.AutoValue;
import com.google.common.base.CaseFormat;
import com.google.common.base.Enums;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
@AutoValue
public abstract class Action {
public enum Status {
COMPLETED, IN_PROGRESS, ERRORED;
Status() {}
public static Status fromValue(String value) {
Optional<Status> status = Enums.getIfPresent(Status.class, value.toUpperCase());
if (!status.isPresent()) {
String upperCamelValue = CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_UNDERSCORE, value.toLowerCase());
status = Enums.getIfPresent(Status.class, upperCamelValue);
}
checkArgument(status.isPresent(), "Expected one of %s but was", Joiner.on(',').join(Status.values()), value);
return status.get();
}
}
public abstract int id();
public abstract Status status();
public abstract String type();
public abstract Date startedAt();
@Nullable public abstract Date completedAt();
public abstract Integer resourceId();
public abstract String resourceType();
@Nullable public abstract Region region();
@Nullable public abstract String regionSlug();
@SerializedNames({ "id", "status", "type", "started_at", "completed_at", "resource_id", "resource_type",
"region", "region_slug" })
public static Action create(int id, Status status, String type, Date startedAt, Date completedAt, int resourceId,
String resourceType, Region region, String regionSlug) {
return new AutoValue_Action(id, status, type, startedAt, completedAt, resourceId, resourceType, region,
regionSlug);
}
Action() {}
}

View File

@ -0,0 +1,43 @@
/*
* 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.digitalocean2.domain;
import java.util.List;
import org.jclouds.javax.annotation.Nullable;
import org.jclouds.json.SerializedNames;
import com.google.auto.value.AutoValue;
@AutoValue
public abstract class Backup {
public abstract int id();
public abstract String name();
public abstract String distribution();
@Nullable public abstract String slug();
public abstract boolean isPublic();
public abstract List<String> regions();
public abstract int minDiskSize();
@SerializedNames({ "id", "name", "distribution", "slug", "public", "regions", "min_disk_size" })
public static Backup create(int id, String name, String distribution, String slug, boolean isPublic,
List<String> regions, int minDiskSize) {
return new AutoValue_Backup(id, name, distribution, slug, isPublic, regions, minDiskSize);
}
Backup() {}
}

View File

@ -0,0 +1,69 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.digitalocean2.domain;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Iterables.tryFind;
import static java.util.Arrays.asList;
import java.util.List;
import org.jclouds.compute.domain.OsFamily;
import com.google.common.base.Predicate;
/**
* DigitalOcean image distributions.
*/
public enum Distribution {
ARCHLINUX(OsFamily.ARCH, "Arch Linux"),
CENTOS(OsFamily.CENTOS, "CentOS"),
DEBIAN(OsFamily.DEBIAN, "Debian"),
FEDORA(OsFamily.FEDORA, "Fedora"),
UBUNTU(OsFamily.UBUNTU, "Ubuntu"),
UNRECOGNIZED(OsFamily.UNRECOGNIZED, "");
private static final List<Distribution> values = asList(Distribution.values());
private final OsFamily osFamily;
private final String value;
private Distribution(OsFamily osFamily, String value) {
this.osFamily = checkNotNull(osFamily, "osFamily cannot be null");
this.value = checkNotNull(value, "value cannot be null");
}
public OsFamily osFamily() {
return this.osFamily;
}
public String value() {
return this.value;
}
public static Distribution fromValue(String value) {
return tryFind(values, hasValue(value)).or(UNRECOGNIZED);
}
private static Predicate<Distribution> hasValue(final String value) {
return new Predicate<Distribution>() {
@Override
public boolean apply(Distribution input) {
return input.value.equalsIgnoreCase(value);
}
};
}
}

View File

@ -0,0 +1,92 @@
/*
* 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.digitalocean2.domain;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableList.copyOf;
import static com.google.common.collect.Iterables.concat;
import java.util.Date;
import java.util.List;
import java.util.Set;
import org.jclouds.javax.annotation.Nullable;
import org.jclouds.json.SerializedNames;
import com.google.auto.value.AutoValue;
import com.google.common.base.Enums;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
@AutoValue
public abstract class Droplet {
public enum Status {
NEW, ACTIVE, ARCHIVE, OFF;
public static Status fromValue(String value) {
Optional<Status> status = Enums.getIfPresent(Status.class, value.toUpperCase());
checkArgument(status.isPresent(), "Expected one of %s but was %s", Joiner.on(',').join(Status.values()), value);
return status.get();
}
}
public abstract int id();
public abstract String name();
public abstract int memory();
public abstract int vcpus();
public abstract int disk();
public abstract boolean locked();
public abstract Date createdAt();
public abstract Status status();
public abstract List<Integer> backupsIds();
public abstract List<Integer> snapshotIds();
public abstract List<String> features();
@Nullable public abstract Region region();
@Nullable public abstract Image image();
@Nullable public abstract Size size();
public abstract String sizeSlug();
@Nullable public abstract Networks networks();
@Nullable public abstract Kernel kernel();
@SerializedNames({ "id", "name", "memory", "vcpus", "disk", "locked", "created_at", "status", "backup_ids",
"snapshot_ids", "features", "region", "image", "size", "size_slug", "networks", "kernel" })
public static Droplet create(int id, String name, int memory, int vcpus, int disk, boolean locked, Date createdAt,
Status status, List<Integer> backupIds, List<Integer> snapshotIds, List<String> features, Region region,
Image image, Size size, String sizeSlug, Networks network, Kernel kernel) {
return new AutoValue_Droplet(id, name, memory, vcpus, disk, locked, createdAt, status,
backupIds == null ? ImmutableList.<Integer> of() : copyOf(backupIds),
snapshotIds == null ? ImmutableList.<Integer> of() : copyOf(snapshotIds), copyOf(features), region, image,
size, sizeSlug, network, kernel);
}
public Set<Networks.Address> getPublicAddresses() {
return FluentIterable.from(concat(networks().ipv4(), networks().ipv6()))
.filter(Networks.Predicates.publicNetworks())
.toSet();
}
public Set<Networks.Address> getPrivateAddresses() {
return FluentIterable.from(concat(networks().ipv4(), networks().ipv6()))
.filter(Networks.Predicates.privateNetworks())
.toSet();
}
Droplet() {}
}

View File

@ -0,0 +1,66 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.digitalocean2.domain;
import static com.google.common.collect.ImmutableList.copyOf;
import java.net.URI;
import java.util.List;
import org.jclouds.json.SerializedNames;
import com.google.auto.value.AutoValue;
@AutoValue
public abstract class DropletCreate {
public abstract Droplet droplet();
public abstract Links links();
@AutoValue
public abstract static class Links {
@AutoValue
public abstract static class ActionLink {
public abstract int id();
public abstract String rel();
public abstract URI href();
@SerializedNames({"id", "rel", "href"})
public static ActionLink create(int id, String rel, URI href) {
return new AutoValue_DropletCreate_Links_ActionLink(id, rel, href);
}
ActionLink() {}
}
public abstract List<ActionLink> actions();
@SerializedNames({ "actions" })
public static Links create(List<ActionLink> actions) {
return new AutoValue_DropletCreate_Links(copyOf(actions));
}
Links() {}
}
@SerializedNames({ "droplet", "links" })
public static DropletCreate create(Droplet droplet, Links links) {
return new AutoValue_DropletCreate(droplet, links);
}
DropletCreate() {}
}

View File

@ -0,0 +1,48 @@
/*
* 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.digitalocean2.domain;
import static com.google.common.collect.ImmutableList.copyOf;
import java.util.Date;
import java.util.List;
import org.jclouds.javax.annotation.Nullable;
import org.jclouds.json.SerializedNames;
import com.google.auto.value.AutoValue;
@AutoValue
public abstract class Image {
public abstract int id();
public abstract String name();
public abstract String type();
public abstract String distribution();
@Nullable public abstract String slug();
public abstract boolean isPublic();
public abstract List<String> regions();
public abstract Date createdAt();
@SerializedNames({ "id", "name", "type", "distribution", "slug", "public", "regions", "created_at" })
public static Image create(int id, String name, String type, String distribution, String slug, boolean isPublic,
List<String> regions, Date createdAt) {
return new AutoValue_Image(id, name, type, distribution, slug, isPublic, copyOf(regions), createdAt);
}
Image() {}
}

View File

@ -0,0 +1,35 @@
/*
* 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.digitalocean2.domain;
import org.jclouds.json.SerializedNames;
import com.google.auto.value.AutoValue;
@AutoValue
public abstract class Kernel {
public abstract int id();
public abstract String name();
public abstract String version();
@SerializedNames({ "id", "name", "version" })
public static Kernel create(int id, String name, String version) {
return new AutoValue_Kernel(id, name, version);
}
Kernel() {}
}

View File

@ -0,0 +1,39 @@
/*
* 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.digitalocean2.domain;
import java.security.PublicKey;
import org.jclouds.json.SerializedNames;
import com.google.auto.value.AutoValue;
@AutoValue
public abstract class Key {
public abstract int id();
public abstract String name();
public abstract String fingerprint();
public abstract PublicKey publicKey();
@SerializedNames({ "id", "name", "fingerprint", "public_key" })
public static Key create(int id, String name, String fingerprint, PublicKey publicKey) {
return new AutoValue_Key(id, name, fingerprint, publicKey);
}
Key() {}
}

View File

@ -0,0 +1,77 @@
/*
* 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.digitalocean2.domain;
import static com.google.common.collect.ImmutableList.copyOf;
import java.util.List;
import org.jclouds.json.SerializedNames;
import com.google.auto.value.AutoValue;
import com.google.common.base.Predicate;
@AutoValue
public abstract class Networks {
@AutoValue
public abstract static class Address {
public abstract String ip();
public abstract String netmask();
public abstract String gateway();
public abstract String type();
@SerializedNames({ "ip_address", "netmask", "gateway", "type"})
public static Address create(String ip, String netmask, String gateway, String type) {
return new AutoValue_Networks_Address(ip, netmask, gateway, type);
}
Address() {}
}
public abstract List<Address> ipv4();
public abstract List<Address> ipv6();
@SerializedNames({ "v4", "v6" })
public static Networks create(List<Address> ipv4, List<Address> ipv6) {
return new AutoValue_Networks(copyOf(ipv4), copyOf(ipv6));
}
Networks() {}
public static class Predicates {
public static Predicate<Address> publicNetworks() {
return new Predicate<Address>() {
@Override
public boolean apply(Address network) {
return network.type().equals("public");
}
};
}
public static Predicate<Address> privateNetworks() {
return new Predicate<Address>() {
@Override
public boolean apply(Address network) {
return network.type().equals("private");
}
};
}
}
}

View File

@ -0,0 +1,60 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.digitalocean2.domain;
import static com.google.common.base.Strings.nullToEmpty;
import static java.util.regex.Pattern.compile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.auto.value.AutoValue;
/**
* The operating system of an image.
* <p>
* This class parses the <code>name</code> string (e.g. "Ubuntu 12.10 x64") of the images and properly sets each field
* to the right value.
*/
@AutoValue
public abstract class OperatingSystem {
// Parse something like "12.10 x64" or "Ubuntu 12.10.1 x64" and matches the version and architecture
private static final Pattern VERSION_PATTERN = compile("(?:[a-zA-Z\\s]*\\s+)?(\\d+(?:\\.?\\d+)*)?(?:\\s*(x\\d{2}))?.*");
private static final String IS_64_BIT = "x64";
public abstract Distribution distribution();
public abstract String version();
public abstract String arch();
public static OperatingSystem create(String name, String distribution) {
return new AutoValue_OperatingSystem(Distribution.fromValue(distribution), match(VERSION_PATTERN, name, 1),
match(VERSION_PATTERN, name, 2));
}
public boolean is64bit() {
return IS_64_BIT.equals(arch());
}
OperatingSystem() {}
private static String match(final Pattern pattern, final String input, int group) {
Matcher m = pattern.matcher(input);
return m.find() ? nullToEmpty(m.group(group)) : "";
}
}

View File

@ -0,0 +1,39 @@
/*
* 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.digitalocean2.domain;
import java.util.List;
import org.jclouds.json.SerializedNames;
import com.google.auto.value.AutoValue;
@AutoValue
public abstract class Region {
public abstract String slug();
public abstract String name();
public abstract List<String> sizes();
public abstract boolean available();
public abstract List<String> features();
@SerializedNames({ "slug", "name", "sizes", "available", "features" })
public static Region create(String slug, String name, List<String> sizes, boolean available, List<String> features) {
return new AutoValue_Region(slug, name, sizes, available, features);
}
Region() {}
}

View File

@ -0,0 +1,46 @@
/*
* 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.digitalocean2.domain;
import java.util.List;
import org.jclouds.json.SerializedNames;
import com.google.auto.value.AutoValue;
@AutoValue
public abstract class Size {
public abstract String slug();
public abstract boolean available();
public abstract float transfer();
public abstract float priceMonthly();
public abstract float priceHourly();
public abstract int memory();
public abstract int vcpus();
public abstract int disk();
public abstract List<String> regions();
@SerializedNames({ "slug", "available", "transfer", "price_monthly", "price_hourly", "memory", "vcpus", "disk",
"regions" })
public static Size create(String slug, boolean available, float transfer, float priceMonthly, float priceHourly,
int memory, int vcpus, int disk, List<String> regions) {
return new AutoValue_Size(slug, available, transfer, priceMonthly, priceHourly, memory, vcpus, disk, regions);
}
Size() {}
}

View File

@ -0,0 +1,47 @@
/*
* 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.digitalocean2.domain;
import java.util.Date;
import java.util.List;
import org.jclouds.javax.annotation.Nullable;
import org.jclouds.json.SerializedNames;
import com.google.auto.value.AutoValue;
@AutoValue
public abstract class Snapshot {
public abstract int id();
public abstract String name();
public abstract String type();
public abstract String distribution();
@Nullable public abstract String slug();
public abstract boolean isPublic();
public abstract List<String> regions();
public abstract int minDiskSize();
public abstract Date createdAt();
@SerializedNames({ "id", "name", "type", "distribution", "slug", "public", "regions", "min_disk_size", "created_at"})
public static Snapshot create(int id, String name, String type, String distribution, String slug, boolean isPublic,
List<String> regions, int minDiskSize, Date createdAt) {
return new AutoValue_Snapshot(id, name, type, distribution, slug, isPublic, regions, minDiskSize, createdAt);
}
Snapshot() {}
}

View File

@ -0,0 +1,111 @@
/*
* 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.digitalocean2.domain.internal;
import static com.google.common.base.Preconditions.checkNotNull;
import java.net.URI;
import java.util.Iterator;
import java.util.List;
import org.jclouds.collect.IterableWithMarker;
import org.jclouds.javax.annotation.Nullable;
import org.jclouds.json.SerializedNames;
import com.google.auto.value.AutoValue;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
/**
* Base class for all collections that return paginated results.
*/
public abstract class PaginatedCollection<T> extends IterableWithMarker<T> {
@AutoValue
public abstract static class Meta {
public abstract long total();
@SerializedNames({ "total" })
public static Meta create(long total) {
return new AutoValue_PaginatedCollection_Meta(total);
}
Meta() { }
}
@AutoValue
public abstract static class Links {
@AutoValue
public abstract static class Pages {
@Nullable public abstract URI first();
@Nullable public abstract URI prev();
@Nullable public abstract URI next();
@Nullable public abstract URI last();
@SerializedNames({ "first", "prev", "next", "last" })
public static Pages create(URI first, URI next, URI prev, URI last) {
return new AutoValue_PaginatedCollection_Links_Pages(first, next, prev, last);
}
Pages() { }
}
@Nullable public abstract Pages pages();
@SerializedNames({ "pages" })
public static Links create(Pages pages) {
return new AutoValue_PaginatedCollection_Links(pages);
}
Links() { }
}
private final List<T> items;
private final Meta meta;
private final Links links;
protected PaginatedCollection(List<T> items, Meta meta, Links links) {
this.items = ImmutableList.copyOf(checkNotNull(items, "items cannot be null"));
this.meta = checkNotNull(meta, "meta cannot be null");
this.links = checkNotNull(links, "links cannot be null");
}
public List<T> items() {
return items;
}
public Meta meta() {
return meta;
}
public Links links() {
return links;
}
@Override public Iterator<T> iterator() {
return items.iterator();
}
@Override public Optional<Object> nextMarker() {
if (links.pages() == null) {
return Optional.absent();
}
return Optional.fromNullable((Object) links.pages().next());
}
}

View File

@ -0,0 +1,179 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.digitalocean2.domain.options;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import org.jclouds.http.HttpRequest;
import org.jclouds.javax.annotation.Nullable;
import org.jclouds.json.SerializedNames;
import org.jclouds.rest.MapBinder;
import org.jclouds.rest.binders.BindToJsonPayload;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableSet;
/**
* Options to customize droplet creation.
*/
public class CreateDropletOptions implements MapBinder {
@Inject private BindToJsonPayload jsonBinder;
private final Set<Integer> sshKeys;
private final boolean backupsEnabled;
private final boolean ipv6Enabled;
private final boolean privateNetworking;
private final String userData;
private CreateDropletOptions(Set<Integer> sshKeys, boolean backupsEnabled, boolean ipv6Enabled,
boolean privateNetworking, @Nullable String userData) {
this.sshKeys = sshKeys;
this.backupsEnabled = backupsEnabled;
this.ipv6Enabled = ipv6Enabled;
this.privateNetworking = privateNetworking;
this.userData = userData;
}
@AutoValue
abstract static class DropletRequest {
abstract String name();
abstract String region();
abstract String size();
abstract String image();
abstract Set<Integer> sshKeys();
abstract Boolean backups();
abstract Boolean ipv6();
abstract Boolean privateNetworking();
@Nullable abstract String userData();
@SerializedNames({"name", "region", "size", "image", "ssh_keys", "backups", "ipv6", "private_networking", "user_data"})
static DropletRequest create(String name, String region, String size, String image, Set<Integer> sshKeys,
Boolean backups, Boolean ipv6, Boolean privateNetworking, String userData) {
return new AutoValue_CreateDropletOptions_DropletRequest(name, region, size, image, sshKeys, backups, ipv6,
privateNetworking, userData);
}
DropletRequest() {}
}
@Override
public <R extends HttpRequest> R bindToRequest(R request, Map<String, Object> postParams) {
DropletRequest droplet = DropletRequest.create(checkNotNull(postParams.get("name"), "name parameter not present").toString(),
checkNotNull(postParams.get("region"), "region parameter not present").toString(),
checkNotNull(postParams.get("size"), "size parameter not present").toString(),
checkNotNull(postParams.get("image"), "image parameter not present").toString(),
sshKeys, backupsEnabled, ipv6Enabled, privateNetworking, userData);
return bindToRequest(request, droplet);
}
@Override
public <R extends HttpRequest> R bindToRequest(R request, Object input) {
return jsonBinder.bindToRequest(request, input);
}
public Set<Integer> getSshKeys() {
return sshKeys;
}
public Boolean getPrivateNetworking() {
return privateNetworking;
}
public Boolean getBackupsEnabled() {
return backupsEnabled;
}
public boolean isIpv6Enabled() {
return ipv6Enabled;
}
public String getUserData() {
return userData;
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private ImmutableSet.Builder<Integer> sshKeyIds = ImmutableSet.builder();
private boolean backupsEnabled;
private boolean ipv6Enabled;
private boolean privateNetworking;
private String userData;
/**
* Adds a set of ssh key ids to be added to the droplet.
*/
public Builder addSshKeyIds(Iterable<Integer> sshKeyIds) {
this.sshKeyIds.addAll(sshKeyIds);
return this;
}
/**
* Adds an ssh key id to be added to the droplet.
*/
public Builder addSshKeyId(int sshKeyId) {
this.sshKeyIds.add(sshKeyId);
return this;
}
/**
* Enables a private network interface if the region supports private
* networking.
*/
public Builder privateNetworking(boolean privateNetworking) {
this.privateNetworking = privateNetworking;
return this;
}
/**
* Enabled backups for the droplet.
*/
public Builder backupsEnabled(boolean backupsEnabled) {
this.backupsEnabled = backupsEnabled;
return this;
}
/**
* Sets the user data for the droplet.
*/
public Builder userData(String userData) {
this.userData = userData;
return this;
}
/**
* Enables/disables IPv6 for the droplet.
*/
public Builder ipv6Enabled(boolean ipv6Enabled) {
this.ipv6Enabled = ipv6Enabled;
return this;
}
public CreateDropletOptions build() {
return new CreateDropletOptions(sshKeyIds.build(), backupsEnabled, ipv6Enabled, privateNetworking, userData);
}
}
}

View File

@ -0,0 +1,74 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.digitalocean2.domain.options;
/**
* Custom options to filter the list of images.
*/
public class ImageListOptions extends ListOptions {
public static final String TYPE_PARAM = "type";
public static final String PRIVATE_PARAM = "private";
/**
* Configures the type of the images to be retrieved.
*/
public ImageListOptions type(String type) {
queryParameters.put(TYPE_PARAM, type);
return this;
}
/**
* Get the images of the current user.
*/
public ImageListOptions privateImages(boolean privateImages) {
queryParameters.put(PRIVATE_PARAM, String.valueOf(privateImages));
return this;
}
@Override public ImageListOptions perPage(int perPage) {
super.perPage(perPage);
return this;
}
@Override public ImageListOptions page(int page) {
super.page(page);
return this;
}
public static final class Builder {
/**
* @see {@link ImageListOptions#type(String)}
*/
public static ImageListOptions type(String type) {
return new ImageListOptions().type(type);
}
/**
* @see {@link ImageListOptions#privateImages(boolean)}
*/
public static ImageListOptions privateImages(boolean privateImages) {
return new ImageListOptions().privateImages(privateImages);
}
/**
* @see {@link ImageListOptions#page(int)}
*/
public static ImageListOptions page(int page) {
return new ImageListOptions().page(page);
}
}
}

View File

@ -0,0 +1,60 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.digitalocean2.domain.options;
import org.jclouds.http.options.BaseHttpRequestOptions;
/**
* Options to customize how paginated lists are returned.
*/
public class ListOptions extends BaseHttpRequestOptions {
public static final String PAGE_PARAM = "page";
public static final String PER_PAGE_PARAM = "per_page";
/**
* Configures the number of entries to return in each page.
*/
public ListOptions perPage(int perPage) {
queryParameters.put(PER_PAGE_PARAM, String.valueOf(perPage));
return this;
}
/**
* Configures the number of the page to be returned.
*/
public ListOptions page(int page) {
queryParameters.put(PAGE_PARAM, String.valueOf(page));
return this;
}
public static final class Builder {
/**
* @see {@link ListOptions#perPage(int)}
*/
public static ListOptions perPage(int perPage) {
return new ListOptions().perPage(perPage);
}
/**
* @see {@link ListOptions#page(int)}
*/
public static ListOptions page(int page) {
return new ListOptions().page(page);
}
}
}

View File

@ -0,0 +1,81 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.digitalocean2.exceptions;
import static org.jclouds.digitalocean2.handlers.RateLimitRetryHandler.millisUntilNextAvailableRequest;
import org.jclouds.http.HttpResponse;
import org.jclouds.rest.RateLimitExceededException;
import com.google.common.annotations.Beta;
import com.google.common.base.Predicate;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
/**
* Provides detailed information for rate limit exceptions.
*/
@Beta
public class DigitalOcean2RateLimitExceededException extends RateLimitExceededException {
private static final long serialVersionUID = 1L;
private static final String RATE_LIMIT_HEADER_PREFIX = "RateLimit-";
private Integer totalRequestsPerHour;
private Integer remainingRequests;
private Long timeToNextAvailableRequest;
public DigitalOcean2RateLimitExceededException(HttpResponse response) {
super(response.getStatusLine() + "\n" + rateLimitHeaders(response));
parseRateLimitInfo(response);
}
public DigitalOcean2RateLimitExceededException(HttpResponse response, Throwable cause) {
super(response.getStatusLine() + "\n" + rateLimitHeaders(response), cause);
parseRateLimitInfo(response);
}
public Integer totalRequestsPerHour() {
return totalRequestsPerHour;
}
public Integer remainingRequests() {
return remainingRequests;
}
public Long timeToNextAvailableRequest() {
return timeToNextAvailableRequest;
}
private void parseRateLimitInfo(HttpResponse response) {
String limit = response.getFirstHeaderOrNull("RateLimit-Limit");
String remaining = response.getFirstHeaderOrNull("RateLimit-Remaining");
String reset = response.getFirstHeaderOrNull("RateLimit-Reset");
totalRequestsPerHour = limit == null ? null : Integer.valueOf(limit);
remainingRequests = remaining == null ? null : Integer.valueOf(remaining);
timeToNextAvailableRequest = reset == null ? null : millisUntilNextAvailableRequest(Long.valueOf(reset));
}
private static Multimap<String, String> rateLimitHeaders(HttpResponse response) {
return Multimaps.filterKeys(response.getHeaders(), new Predicate<String>() {
@Override
public boolean apply(String input) {
return input.startsWith(RATE_LIMIT_HEADER_PREFIX);
}
});
}
}

View File

@ -0,0 +1,113 @@
/*
* 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.digitalocean2.features;
import java.beans.ConstructorProperties;
import java.io.Closeable;
import java.net.URI;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.MediaType;
import org.jclouds.Fallbacks.EmptyIterableWithMarkerOnNotFoundOr404;
import org.jclouds.Fallbacks.EmptyPagedIterableOnNotFoundOr404;
import org.jclouds.Fallbacks.NullOnNotFoundOr404;
import org.jclouds.collect.IterableWithMarker;
import org.jclouds.collect.PagedIterable;
import org.jclouds.digitalocean2.DigitalOcean2Api;
import org.jclouds.digitalocean2.domain.Action;
import org.jclouds.digitalocean2.domain.internal.PaginatedCollection;
import org.jclouds.digitalocean2.domain.options.ListOptions;
import org.jclouds.digitalocean2.functions.BaseToPagedIterable;
import org.jclouds.http.functions.ParseJson;
import org.jclouds.javax.annotation.Nullable;
import org.jclouds.json.Json;
import org.jclouds.oauth.v2.filters.OAuthFilter;
import org.jclouds.rest.annotations.Fallback;
import org.jclouds.rest.annotations.RequestFilters;
import org.jclouds.rest.annotations.ResponseParser;
import org.jclouds.rest.annotations.SelectJson;
import org.jclouds.rest.annotations.Transform;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.inject.TypeLiteral;
/**
* Provides access to Actions via the REST API.
*
* @see <a href="https://developers.digitalocean.com/v2/#actions"/>
* @see ActionApi
*/
@Path("/actions")
@RequestFilters(OAuthFilter.class)
@Consumes(MediaType.APPLICATION_JSON)
public interface ActionApi extends Closeable {
@Named("action:list")
@GET
@ResponseParser(ParseActions.class)
@Transform(ParseActions.ToPagedIterable.class)
@Fallback(EmptyPagedIterableOnNotFoundOr404.class)
PagedIterable<Action> list();
@Named("action:list")
@GET
@ResponseParser(ParseActions.class)
@Fallback(EmptyIterableWithMarkerOnNotFoundOr404.class)
IterableWithMarker<Action> list(ListOptions options);
static final class ParseActions extends ParseJson<ParseActions.Actions> {
@Inject ParseActions(Json json) {
super(json, TypeLiteral.get(Actions.class));
}
private static class Actions extends PaginatedCollection<Action> {
@ConstructorProperties({ "actions", "meta", "links" })
public Actions(List<Action> items, Meta meta, Links links) {
super(items, meta, links);
}
}
private static class ToPagedIterable extends BaseToPagedIterable<Action, ListOptions> {
@Inject ToPagedIterable(DigitalOcean2Api api, Function<URI, ListOptions> linkToOptions) {
super(api, linkToOptions);
}
@Override
protected IterableWithMarker<Action> fetchPageUsingOptions(ListOptions options, Optional<Object> arg0) {
return api.actionApi().list(options);
}
}
}
@Named("action:get")
@GET
@SelectJson("action")
@Path("/{id}")
@Fallback(NullOnNotFoundOr404.class)
@Nullable
Action get(@PathParam("id") int id);
}

View File

@ -0,0 +1,350 @@
/*
* 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.digitalocean2.features;
import java.beans.ConstructorProperties;
import java.io.Closeable;
import java.net.URI;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.jclouds.Fallbacks.EmptyIterableWithMarkerOnNotFoundOr404;
import org.jclouds.Fallbacks.EmptyPagedIterableOnNotFoundOr404;
import org.jclouds.Fallbacks.NullOnNotFoundOr404;
import org.jclouds.Fallbacks.VoidOnNotFoundOr404;
import org.jclouds.collect.IterableWithMarker;
import org.jclouds.collect.PagedIterable;
import org.jclouds.digitalocean2.DigitalOcean2Api;
import org.jclouds.digitalocean2.domain.Action;
import org.jclouds.digitalocean2.domain.Backup;
import org.jclouds.digitalocean2.domain.Droplet;
import org.jclouds.digitalocean2.domain.DropletCreate;
import org.jclouds.digitalocean2.domain.Kernel;
import org.jclouds.digitalocean2.domain.Snapshot;
import org.jclouds.digitalocean2.domain.internal.PaginatedCollection;
import org.jclouds.digitalocean2.domain.options.CreateDropletOptions;
import org.jclouds.digitalocean2.domain.options.ListOptions;
import org.jclouds.digitalocean2.functions.BaseToPagedIterable;
import org.jclouds.http.functions.ParseJson;
import org.jclouds.javax.annotation.Nullable;
import org.jclouds.json.Json;
import org.jclouds.oauth.v2.filters.OAuthFilter;
import org.jclouds.rest.annotations.Fallback;
import org.jclouds.rest.annotations.MapBinder;
import org.jclouds.rest.annotations.Payload;
import org.jclouds.rest.annotations.PayloadParam;
import org.jclouds.rest.annotations.RequestFilters;
import org.jclouds.rest.annotations.ResponseParser;
import org.jclouds.rest.annotations.SelectJson;
import org.jclouds.rest.annotations.Transform;
import org.jclouds.rest.binders.BindToJsonPayload;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.inject.TypeLiteral;
/**
* Provides access to Droplets via their REST API.
*
* @see <a href="https://developers.digitalocean.com/v2/#droplets"/>
* @see DropletApi
*/
@Path("/droplets")
@RequestFilters(OAuthFilter.class)
@Consumes(MediaType.APPLICATION_JSON)
public interface DropletApi extends Closeable {
@Named("droplet:list")
@GET
@ResponseParser(ParseDroplets.class)
@Transform(ParseDroplets.ToPagedIterable.class)
@Fallback(EmptyPagedIterableOnNotFoundOr404.class)
PagedIterable<Droplet> list();
@Named("droplet:list")
@GET
@ResponseParser(ParseDroplets.class)
@Fallback(EmptyIterableWithMarkerOnNotFoundOr404.class)
IterableWithMarker<Droplet> list(ListOptions options);
static final class ParseDroplets extends ParseJson<ParseDroplets.Droplets> {
@Inject ParseDroplets(Json json) {
super(json, TypeLiteral.get(Droplets.class));
}
private static class Droplets extends PaginatedCollection<Droplet> {
@ConstructorProperties({ "droplets", "meta", "links" })
public Droplets(List<Droplet> items, Meta meta, Links links) {
super(items, meta, links);
}
}
private static class ToPagedIterable extends BaseToPagedIterable<Droplet, ListOptions> {
@Inject ToPagedIterable(DigitalOcean2Api api, Function<URI, ListOptions> linkToOptions) {
super(api, linkToOptions);
}
@Override
protected IterableWithMarker<Droplet> fetchPageUsingOptions(ListOptions options, Optional<Object> arg0) {
return api.dropletApi().list(options);
}
}
}
@Named("droplet:listkernels")
@GET
@Path("/{id}/kernels")
@ResponseParser(ParseKernels.class)
@Transform(ParseKernels.ToPagedIterable.class)
@Fallback(EmptyPagedIterableOnNotFoundOr404.class)
PagedIterable<Kernel> listKernels(@PathParam("id") int id);
@Named("droplet:listkernels")
@GET
@Path("/{id}/kernels")
@ResponseParser(ParseKernels.class)
@Fallback(EmptyIterableWithMarkerOnNotFoundOr404.class)
IterableWithMarker<Kernel> listKernels(@PathParam("id") int id, ListOptions options);
static final class ParseKernels extends ParseJson<ParseKernels.Kernels> {
@Inject ParseKernels(Json json) {
super(json, TypeLiteral.get(Kernels.class));
}
private static class Kernels extends PaginatedCollection<Kernel> {
@ConstructorProperties({ "kernels", "meta", "links" })
public Kernels(List<Kernel> items, Meta meta, Links links) {
super(items, meta, links);
}
}
private static class ToPagedIterable extends BaseToPagedIterable<Kernel, ListOptions> {
@Inject ToPagedIterable(DigitalOcean2Api api, Function<URI, ListOptions> linkToOptions) {
super(api, linkToOptions);
}
@Override
protected IterableWithMarker<Kernel> fetchPageUsingOptions(ListOptions options, Optional<Object> arg0) {
return api.dropletApi().listKernels((Integer) arg0.get(), options);
}
}
}
@Named("droplet:listsnapshots")
@GET
@Path("/{id}/snapshots")
@ResponseParser(ParseSnapshots.class)
@Transform(ParseSnapshots.ToPagedIterable.class)
@Fallback(EmptyPagedIterableOnNotFoundOr404.class)
PagedIterable<Snapshot> listSnapshots(@PathParam("id") int id);
@Named("droplet:listsnapshots")
@GET
@Path("/{id}/snapshots")
@ResponseParser(ParseSnapshots.class)
@Fallback(EmptyIterableWithMarkerOnNotFoundOr404.class)
IterableWithMarker<Snapshot> listSnapshots(@PathParam("id") int id, ListOptions options);
static final class ParseSnapshots extends ParseJson<ParseSnapshots.Snapshots> {
@Inject ParseSnapshots(Json json) {
super(json, TypeLiteral.get(Snapshots.class));
}
private static class Snapshots extends PaginatedCollection<Snapshot> {
@ConstructorProperties({ "snapshots", "meta", "links" })
public Snapshots(List<Snapshot> items, Meta meta, Links links) {
super(items, meta, links);
}
}
private static class ToPagedIterable extends BaseToPagedIterable<Snapshot, ListOptions> {
@Inject ToPagedIterable(DigitalOcean2Api api, Function<URI, ListOptions> linkToOptions) {
super(api, linkToOptions);
}
@Override
protected IterableWithMarker<Snapshot> fetchPageUsingOptions(ListOptions options, Optional<Object> arg0) {
return api.dropletApi().listSnapshots((Integer) arg0.get(), options);
}
}
}
@Named("droplet:listbackups")
@GET
@Path("/{id}/backups")
@ResponseParser(ParseBackups.class)
@Transform(ParseBackups.ToPagedIterable.class)
@Fallback(EmptyPagedIterableOnNotFoundOr404.class)
PagedIterable<Backup> listBackups(@PathParam("id") int id);
@Named("droplet:listbackups")
@GET
@Path("/{id}/backups")
@ResponseParser(ParseBackups.class)
@Fallback(EmptyIterableWithMarkerOnNotFoundOr404.class)
IterableWithMarker<Backup> listBackups(@PathParam("id") int id, ListOptions options);
static final class ParseBackups extends ParseJson<ParseBackups.Backups> {
@Inject ParseBackups(Json json) {
super(json, TypeLiteral.get(Backups.class));
}
private static class Backups extends PaginatedCollection<Backup> {
@ConstructorProperties({ "backups", "meta", "links" })
public Backups(List<Backup> items, Meta meta, Links links) {
super(items, meta, links);
}
}
private static class ToPagedIterable extends BaseToPagedIterable<Backup, ListOptions> {
@Inject ToPagedIterable(DigitalOcean2Api api, Function<URI, ListOptions> linkToOptions) {
super(api, linkToOptions);
}
@Override
protected IterableWithMarker<Backup> fetchPageUsingOptions(ListOptions options, Optional<Object> arg0) {
return api.dropletApi().listBackups((Integer) arg0.get(), options);
}
}
}
@Named("droplet:actions")
@GET
@Path("/{id}/actions")
@ResponseParser(ParseDropletActions.class)
@Transform(ParseDropletActions.ToPagedIterable.class)
@Fallback(EmptyPagedIterableOnNotFoundOr404.class)
PagedIterable<Action> listActions(@PathParam("id") int id);
@Named("droplet:actions")
@GET
@Path("/{id}/actions")
@ResponseParser(ParseDropletActions.class)
@Fallback(EmptyIterableWithMarkerOnNotFoundOr404.class)
IterableWithMarker<Action> listActions(@PathParam("id") int id, ListOptions options);
static final class ParseDropletActions extends ParseJson<ParseDropletActions.DropletActions> {
@Inject ParseDropletActions(Json json) {
super(json, TypeLiteral.get(DropletActions.class));
}
private static class DropletActions extends PaginatedCollection<Action> {
@ConstructorProperties({ "actions", "meta", "links" })
public DropletActions(List<Action> items, Meta meta, Links links) {
super(items, meta, links);
}
}
private static class ToPagedIterable extends BaseToPagedIterable<Action, ListOptions> {
@Inject ToPagedIterable(DigitalOcean2Api api, Function<URI, ListOptions> linkToOptions) {
super(api, linkToOptions);
}
@Override
protected IterableWithMarker<Action> fetchPageUsingOptions(ListOptions options, Optional<Object> arg0) {
return api.dropletApi().listActions((Integer) arg0.get(), options);
}
}
}
@Named("droplet:create")
@POST
@Produces(MediaType.APPLICATION_JSON)
@MapBinder(BindToJsonPayload.class)
DropletCreate create(@PayloadParam("name") String name, @PayloadParam("region") String region,
@PayloadParam("size") String size, @PayloadParam("image") String image);
@Named("droplet:create")
@POST
@Produces(MediaType.APPLICATION_JSON)
@MapBinder(CreateDropletOptions.class)
DropletCreate create(@PayloadParam("name") String name, @PayloadParam("region") String region,
@PayloadParam("size") String size, @PayloadParam("image") String image, CreateDropletOptions options);
@Named("droplet:get")
@GET
@SelectJson("droplet")
@Path("/{id}")
@Fallback(NullOnNotFoundOr404.class)
@Nullable
Droplet get(@PathParam("id") int id);
@Named("droplet:delete")
@DELETE
@Path("/{id}")
@Fallback(VoidOnNotFoundOr404.class)
void delete(@PathParam("id") int id);
@Named("droplet:reboot")
@POST
@Produces(MediaType.APPLICATION_JSON)
@SelectJson("action")
@Path("/{id}/actions")
@Payload("{\"type\":\"reboot\"}")
Action reboot(@PathParam("id") int id);
@Named("droplet:powercycle")
@POST
@Produces(MediaType.APPLICATION_JSON)
@SelectJson("action")
@Path("/{id}/actions")
@Payload("{\"type\":\"power_cycle\"}")
Action powerCycle(@PathParam("id") int id);
@Named("droplet:shutdown")
@POST
@Produces(MediaType.APPLICATION_JSON)
@SelectJson("action")
@Path("/{id}/actions")
@Payload("{\"type\":\"shutdown\"}")
Action shutdown(@PathParam("id") int id);
@Named("droplet:poweroff")
@POST
@Produces(MediaType.APPLICATION_JSON)
@SelectJson("action")
@Path("/{id}/actions")
@Payload("{\"type\":\"power_off\"}")
Action powerOff(@PathParam("id") int id);
@Named("droplet:poweron")
@POST
@Produces(MediaType.APPLICATION_JSON)
@SelectJson("action")
@Path("/{id}/actions")
@Payload("{\"type\":\"power_on\"}")
Action powerOn(@PathParam("id") int id);
@Named("droplet:snapshot")
@POST
@Produces(MediaType.APPLICATION_JSON)
@SelectJson("action")
@Path("/{id}/actions")
@Payload("%7B\"type\":\"snapshot\",\"name\":\"{name}\"%7D")
Action snapshot(@PathParam("id") int id, @PayloadParam("name") String name);
}

View File

@ -0,0 +1,131 @@
/*
* 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.digitalocean2.features;
import java.beans.ConstructorProperties;
import java.io.Closeable;
import java.net.URI;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.MediaType;
import org.jclouds.Fallbacks.EmptyIterableWithMarkerOnNotFoundOr404;
import org.jclouds.Fallbacks.EmptyPagedIterableOnNotFoundOr404;
import org.jclouds.Fallbacks.NullOnNotFoundOr404;
import org.jclouds.Fallbacks.VoidOnNotFoundOr404;
import org.jclouds.collect.IterableWithMarker;
import org.jclouds.collect.PagedIterable;
import org.jclouds.digitalocean2.DigitalOcean2Api;
import org.jclouds.digitalocean2.domain.Image;
import org.jclouds.digitalocean2.domain.internal.PaginatedCollection;
import org.jclouds.digitalocean2.domain.options.ImageListOptions;
import org.jclouds.digitalocean2.functions.BaseToPagedIterable;
import org.jclouds.http.functions.ParseJson;
import org.jclouds.javax.annotation.Nullable;
import org.jclouds.json.Json;
import org.jclouds.oauth.v2.filters.OAuthFilter;
import org.jclouds.rest.annotations.Fallback;
import org.jclouds.rest.annotations.RequestFilters;
import org.jclouds.rest.annotations.ResponseParser;
import org.jclouds.rest.annotations.SelectJson;
import org.jclouds.rest.annotations.Transform;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.inject.TypeLiteral;
/**
* Provides access to Images via the REST API.
*
* @see <a href="https://developers.digitalocean.com/v2/#images"/>
* @see ImageApi
*/
@Path("/images")
@RequestFilters(OAuthFilter.class)
@Consumes(MediaType.APPLICATION_JSON)
public interface ImageApi extends Closeable {
@Named("image:list")
@GET
@ResponseParser(ParseImages.class)
@Transform(ParseImages.ToPagedIterable.class)
@Fallback(EmptyPagedIterableOnNotFoundOr404.class)
PagedIterable<Image> list();
@Named("image:list")
@GET
@ResponseParser(ParseImages.class)
@Fallback(EmptyIterableWithMarkerOnNotFoundOr404.class)
IterableWithMarker<Image> list(ImageListOptions options);
static final class ParseImages extends ParseJson<ParseImages.Images> {
@Inject ParseImages(Json json) {
super(json, TypeLiteral.get(Images.class));
}
private static class Images extends PaginatedCollection<Image> {
@ConstructorProperties({ "images", "meta", "links" })
public Images(List<Image> items, Meta meta, Links links) {
super(items, meta, links);
}
}
private static class ToPagedIterable extends BaseToPagedIterable<Image, ImageListOptions> {
@Inject ToPagedIterable(DigitalOcean2Api api, Function<URI, ImageListOptions> linkToOptions) {
super(api, linkToOptions);
}
@Override
protected IterableWithMarker<Image> fetchPageUsingOptions(ImageListOptions options, Optional<Object> arg0) {
return api.imageApi().list(options);
}
}
}
@Named("image:get")
@GET
@SelectJson("image")
@Path("/{id}")
@Fallback(NullOnNotFoundOr404.class)
@Nullable
Image get(@PathParam("id") int id);
@Named("image:get")
@GET
@SelectJson("image")
@Path("/{slug}")
@Fallback(NullOnNotFoundOr404.class)
@Nullable
Image get(@PathParam("slug") String slug);
@Named("image:delete")
@DELETE
@Path("/{id}")
@Fallback(VoidOnNotFoundOr404.class)
void delete(@PathParam("id") int id);
//TODO: Add delete and create
}

View File

@ -0,0 +1,164 @@
/*
* 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.digitalocean2.features;
import java.beans.ConstructorProperties;
import java.io.Closeable;
import java.net.URI;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.jclouds.Fallbacks.EmptyIterableWithMarkerOnNotFoundOr404;
import org.jclouds.Fallbacks.EmptyPagedIterableOnNotFoundOr404;
import org.jclouds.Fallbacks.NullOnNotFoundOr404;
import org.jclouds.Fallbacks.VoidOnNotFoundOr404;
import org.jclouds.collect.IterableWithMarker;
import org.jclouds.collect.PagedIterable;
import org.jclouds.digitalocean2.DigitalOcean2Api;
import org.jclouds.digitalocean2.domain.Key;
import org.jclouds.digitalocean2.domain.internal.PaginatedCollection;
import org.jclouds.digitalocean2.domain.options.ListOptions;
import org.jclouds.digitalocean2.functions.BaseToPagedIterable;
import org.jclouds.http.functions.ParseJson;
import org.jclouds.javax.annotation.Nullable;
import org.jclouds.json.Json;
import org.jclouds.oauth.v2.filters.OAuthFilter;
import org.jclouds.rest.annotations.Fallback;
import org.jclouds.rest.annotations.MapBinder;
import org.jclouds.rest.annotations.PayloadParam;
import org.jclouds.rest.annotations.RequestFilters;
import org.jclouds.rest.annotations.ResponseParser;
import org.jclouds.rest.annotations.SelectJson;
import org.jclouds.rest.annotations.Transform;
import org.jclouds.rest.binders.BindToJsonPayload;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.inject.TypeLiteral;
/**
* Provides access to Keys via the REST API.
*
* @see <a href="https://developers.digitalocean.com/v2/#keys"/>
* @see KeyApi
*/
@Path("/account/keys")
@RequestFilters(OAuthFilter.class)
@Consumes(MediaType.APPLICATION_JSON)
public interface KeyApi extends Closeable {
@Named("key:list")
@GET
@ResponseParser(ParseKeys.class)
@Transform(ParseKeys.ToPagedIterable.class)
@Fallback(EmptyPagedIterableOnNotFoundOr404.class)
PagedIterable<Key> list();
@Named("key:list")
@GET
@ResponseParser(ParseKeys.class)
@Fallback(EmptyIterableWithMarkerOnNotFoundOr404.class)
IterableWithMarker<Key> list(ListOptions options);
static final class ParseKeys extends ParseJson<ParseKeys.Keys> {
@Inject ParseKeys(Json json) {
super(json, TypeLiteral.get(Keys.class));
}
private static class Keys extends PaginatedCollection<Key> {
@ConstructorProperties({ "ssh_keys", "meta", "links" })
public Keys(List<Key> items, Meta meta, Links links) {
super(items, meta, links);
}
}
private static class ToPagedIterable extends BaseToPagedIterable<Key, ListOptions> {
@Inject ToPagedIterable(DigitalOcean2Api api, Function<URI, ListOptions> linkToOptions) {
super(api, linkToOptions);
}
@Override
protected IterableWithMarker<Key> fetchPageUsingOptions(ListOptions options, Optional<Object> arg0) {
return api.keyApi().list(options);
}
}
}
@Named("key:create")
@POST
@Produces(MediaType.APPLICATION_JSON)
@SelectJson("ssh_key")
@MapBinder(BindToJsonPayload.class)
Key create(@PayloadParam("name") String name, @PayloadParam("public_key") String key);
@Named("key:get")
@GET
@SelectJson("ssh_key")
@Path("/{id}")
@Fallback(NullOnNotFoundOr404.class)
@Nullable
Key get(@PathParam("id") int id);
@Named("key:get")
@GET
@SelectJson("ssh_key")
@Path("/{fingerprint}")
@Fallback(NullOnNotFoundOr404.class)
@Nullable
Key get(@PathParam("fingerprint") String fingerprint);
@Named("key:update")
@PUT
@Produces(MediaType.APPLICATION_JSON)
@SelectJson("ssh_key")
@Path("/{id}")
@MapBinder(BindToJsonPayload.class)
Key update(@PathParam("id") int id, @PayloadParam("name") String name);
@Named("key:update")
@PUT
@Produces(MediaType.APPLICATION_JSON)
@SelectJson("ssh_key")
@Path("/{fingerprint}")
@MapBinder(BindToJsonPayload.class)
Key update(@PathParam("fingerprint") String fingerprint, @PayloadParam("name") String name);
@Named("key:delete")
@DELETE
@Path("/{id}")
@Fallback(VoidOnNotFoundOr404.class)
void delete(@PathParam("id") int id);
@Named("key:delete")
@DELETE
@Path("/{fingerprint}")
@Fallback(VoidOnNotFoundOr404.class)
void delete(@PathParam("fingerprint") String fingerprint);
}

View File

@ -0,0 +1,107 @@
/*
* 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.digitalocean2.features;
import java.beans.ConstructorProperties;
import java.io.Closeable;
import java.net.URI;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;
import org.jclouds.Fallbacks.EmptyIterableWithMarkerOnNotFoundOr404;
import org.jclouds.Fallbacks.EmptyPagedIterableOnNotFoundOr404;
import org.jclouds.collect.IterableWithMarker;
import org.jclouds.collect.PagedIterable;
import org.jclouds.digitalocean2.DigitalOcean2Api;
import org.jclouds.digitalocean2.domain.Region;
import org.jclouds.digitalocean2.domain.internal.PaginatedCollection;
import org.jclouds.digitalocean2.domain.options.ListOptions;
import org.jclouds.digitalocean2.functions.BaseToPagedIterable;
import org.jclouds.http.functions.ParseJson;
import org.jclouds.json.Json;
import org.jclouds.oauth.v2.filters.OAuthFilter;
import org.jclouds.rest.annotations.Fallback;
import org.jclouds.rest.annotations.RequestFilters;
import org.jclouds.rest.annotations.ResponseParser;
import org.jclouds.rest.annotations.Transform;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.inject.TypeLiteral;
/**
* Provides access to Regions via the REST API.
*/
@Path("/regions")
@RequestFilters(OAuthFilter.class)
@Consumes(MediaType.APPLICATION_JSON)
public interface RegionApi extends Closeable {
/**
* Get the list of all regions.
*
* @return The (paginated) list of all regions.
*/
@Named("region:list")
@GET
@ResponseParser(ParseRegions.class)
@Transform(ParseRegions.ToPagedIterable.class)
@Fallback(EmptyPagedIterableOnNotFoundOr404.class)
PagedIterable<Region> list();
/**
* Get a single page of the region list.
*
* @param options The options to configure the page to get and the size of the page.
* @return The page with the requested regions.
*/
@Named("region:list")
@GET
@ResponseParser(ParseRegions.class)
@Fallback(EmptyIterableWithMarkerOnNotFoundOr404.class)
IterableWithMarker<Region> list(ListOptions options);
static final class ParseRegions extends ParseJson<ParseRegions.Regions> {
@Inject ParseRegions(Json json) {
super(json, TypeLiteral.get(Regions.class));
}
private static class Regions extends PaginatedCollection<Region> {
@ConstructorProperties({ "regions", "meta", "links" })
public Regions(List<Region> items, Meta meta, Links links) {
super(items, meta, links);
}
}
static class ToPagedIterable extends BaseToPagedIterable<Region, ListOptions> {
@Inject ToPagedIterable(DigitalOcean2Api api, Function<URI, ListOptions> linkToOptions) {
super(api, linkToOptions);
}
@Override
protected IterableWithMarker<Region> fetchPageUsingOptions(ListOptions options, Optional<Object> arg0) {
return api.regionApi().list(options);
}
}
}
}

View File

@ -0,0 +1,100 @@
/*
* 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.digitalocean2.features;
import java.beans.ConstructorProperties;
import java.io.Closeable;
import java.net.URI;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;
import org.jclouds.Fallbacks.EmptyIterableWithMarkerOnNotFoundOr404;
import org.jclouds.Fallbacks.EmptyPagedIterableOnNotFoundOr404;
import org.jclouds.collect.IterableWithMarker;
import org.jclouds.collect.PagedIterable;
import org.jclouds.digitalocean2.DigitalOcean2Api;
import org.jclouds.digitalocean2.domain.Size;
import org.jclouds.digitalocean2.domain.internal.PaginatedCollection;
import org.jclouds.digitalocean2.domain.options.ListOptions;
import org.jclouds.digitalocean2.functions.BaseToPagedIterable;
import org.jclouds.http.functions.ParseJson;
import org.jclouds.json.Json;
import org.jclouds.oauth.v2.filters.OAuthFilter;
import org.jclouds.rest.annotations.Fallback;
import org.jclouds.rest.annotations.RequestFilters;
import org.jclouds.rest.annotations.ResponseParser;
import org.jclouds.rest.annotations.Transform;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.inject.TypeLiteral;
/**
* Provides access to Sizes via the REST API.
*
* @see <a href="https://developers.digitalocean.com/v2/#sizes"/>
* @see org.jclouds.digitalocean2.features.SizeApi
*/
@Path("/sizes")
@RequestFilters(OAuthFilter.class)
@Consumes(MediaType.APPLICATION_JSON)
public interface SizeApi extends Closeable {
@Named("size:list")
@GET
@ResponseParser(ParseSizes.class)
@Transform(ParseSizes.ToPagedIterable.class)
@Fallback(EmptyPagedIterableOnNotFoundOr404.class)
PagedIterable<Size> list();
@Named("size:list")
@GET
@ResponseParser(ParseSizes.class)
@Fallback(EmptyIterableWithMarkerOnNotFoundOr404.class)
IterableWithMarker<Size> list(ListOptions options);
static final class ParseSizes extends ParseJson<ParseSizes.Sizes> {
@Inject ParseSizes(Json json) {
super(json, TypeLiteral.get(Sizes.class));
}
private static class Sizes extends PaginatedCollection<Size> {
@ConstructorProperties({ "sizes", "meta", "links" })
public Sizes(List<Size> items, Meta meta, Links links) {
super(items, meta, links);
}
}
private static class ToPagedIterable extends BaseToPagedIterable<Size, ListOptions> {
@Inject ToPagedIterable(DigitalOcean2Api api, Function<URI, ListOptions> linkToOptions) {
super(api, linkToOptions);
}
@Override
protected IterableWithMarker<Size> fetchPageUsingOptions(ListOptions options, Optional<Object> arg0) {
return api.sizeApi().list(options);
}
}
}
}

View File

@ -0,0 +1,59 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.digitalocean2.functions;
import java.net.URI;
import javax.inject.Inject;
import org.jclouds.collect.IterableWithMarker;
import org.jclouds.collect.internal.Arg0ToPagedIterable;
import org.jclouds.digitalocean2.DigitalOcean2Api;
import org.jclouds.digitalocean2.domain.options.ListOptions;
import com.google.common.base.Function;
import com.google.common.base.Optional;
/**
* Base class to implement the functions that build the
* <code>PagedIterable</code>. Subclasses just need to override the
* {@link #fetchPageUsingOptions(ListOptions, Optional)} to invoke the right API
* method with the given options parameter to get the next page.
*/
public abstract class BaseToPagedIterable<T, O extends ListOptions> extends
Arg0ToPagedIterable<T, BaseToPagedIterable<T, O>> {
private final Function<URI, O> linkToOptions;
protected final DigitalOcean2Api api;
@Inject protected BaseToPagedIterable(DigitalOcean2Api api, Function<URI, O> linkToOptions) {
this.api = api;
this.linkToOptions = linkToOptions;
}
protected abstract IterableWithMarker<T> fetchPageUsingOptions(O options, Optional<Object> arg0);
@Override protected Function<Object, IterableWithMarker<T>> markerToNextForArg0(final Optional<Object> arg0) {
return new Function<Object, IterableWithMarker<T>>() {
@Override
public IterableWithMarker<T> apply(Object input) {
O nextOptions = linkToOptions.apply(URI.class.cast(input));
return fetchPageUsingOptions(nextOptions, arg0);
}
};
}
}

View File

@ -0,0 +1,67 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.digitalocean2.functions;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.jclouds.digitalocean2.domain.options.ImageListOptions.PRIVATE_PARAM;
import static org.jclouds.digitalocean2.domain.options.ImageListOptions.TYPE_PARAM;
import static org.jclouds.digitalocean2.domain.options.ListOptions.PAGE_PARAM;
import static org.jclouds.digitalocean2.domain.options.ListOptions.PER_PAGE_PARAM;
import static org.jclouds.digitalocean2.functions.LinkToListOptions.getFirstOrNull;
import static org.jclouds.http.utils.Queries.queryParser;
import java.net.URI;
import org.jclouds.digitalocean2.domain.options.ImageListOptions;
import org.jclouds.digitalocean2.domain.options.ListOptions;
import com.google.common.base.Function;
import com.google.common.collect.Multimap;
/**
* Transforms a link returned by the API into a {@link ListOptions} that can be
* used to perform a request to get another page of a paginated list.
*/
public class LinkToImageListOptions implements Function<URI, ImageListOptions> {
@Override public ImageListOptions apply(URI input) {
checkNotNull(input, "input cannot be null");
Multimap<String, String> queryParams = queryParser().apply(input.getQuery());
String nextPage = getFirstOrNull(PAGE_PARAM, queryParams);
String nextPerPage = getFirstOrNull(PER_PAGE_PARAM, queryParams);
String nextType = getFirstOrNull(TYPE_PARAM, queryParams);
String nextPrivate = getFirstOrNull(PRIVATE_PARAM, queryParams);
ImageListOptions options = new ImageListOptions();
if (nextPage != null) {
options.page(Integer.parseInt(nextPage));
}
if (nextPerPage != null) {
options.perPage(Integer.parseInt(nextPerPage));
}
if (nextType != null) {
options.type(nextType);
}
if (nextPrivate != null) {
options.privateImages(Boolean.parseBoolean(nextPrivate));
}
return options;
}
}

View File

@ -0,0 +1,61 @@
/*
* 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.digitalocean2.functions;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.collect.Iterables.getFirst;
import static org.jclouds.digitalocean2.domain.options.ListOptions.PAGE_PARAM;
import static org.jclouds.digitalocean2.domain.options.ListOptions.PER_PAGE_PARAM;
import static org.jclouds.http.utils.Queries.queryParser;
import java.net.URI;
import org.jclouds.digitalocean2.domain.options.ListOptions;
import com.google.common.base.Function;
import com.google.common.collect.Multimap;
/**
* Transforms a link returned by the API into a {@link ListOptions} that can be
* used to perform a request to get another page of a paginated list.
*/
public class LinkToListOptions implements Function<URI, ListOptions> {
@Override public ListOptions apply(URI input) {
checkNotNull(input, "input cannot be null");
Multimap<String, String> queryParams = queryParser().apply(input.getQuery());
String nextPage = getFirstOrNull(PAGE_PARAM, queryParams);
String nextPerPage = getFirstOrNull(PER_PAGE_PARAM, queryParams);
ListOptions options = new ListOptions();
if (nextPage != null) {
options.page(Integer.parseInt(nextPage));
}
if (nextPerPage != null) {
options.perPage(Integer.parseInt(nextPerPage));
}
return options;
}
public static String getFirstOrNull(String key, Multimap<String, String> params) {
return params.containsKey(key) ? emptyToNull(getFirst(params.get(key), null)) : null;
}
}

View File

@ -0,0 +1,72 @@
/*
* 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.digitalocean2.handlers;
import static org.jclouds.http.HttpUtils.closeClientButKeepContentStream;
import javax.inject.Singleton;
import org.jclouds.digitalocean2.exceptions.DigitalOcean2RateLimitExceededException;
import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpErrorHandler;
import org.jclouds.http.HttpResponse;
import org.jclouds.http.HttpResponseException;
import org.jclouds.rest.AuthorizationException;
import org.jclouds.rest.InsufficientResourcesException;
import org.jclouds.rest.ResourceNotFoundException;
/**
* This will parse and set an appropriate exception on the command object.
*/
@Singleton
public class DigitalOcean2ErrorHandler implements HttpErrorHandler {
public void handleError(HttpCommand command, HttpResponse response) {
// it is important to always read fully and close streams
byte[] data = closeClientButKeepContentStream(response);
String message = data != null ? new String(data) : null;
Exception exception = message != null ? new HttpResponseException(command, response, message)
: new HttpResponseException(command, response);
message = message != null ? message : String.format("%s -> %s", command.getCurrentRequest().getRequestLine(),
response.getStatusLine());
switch (response.getStatusCode()) {
case 400:
break;
case 401:
case 403:
if (message.contains("droplet limit")) {
exception = new InsufficientResourcesException(message, exception);
} else {
exception = new AuthorizationException(message, exception);
}
break;
case 404:
if (!command.getCurrentRequest().getMethod().equals("DELETE")) {
exception = new ResourceNotFoundException(message, exception);
}
break;
case 409:
exception = new IllegalStateException(message, exception);
break;
case 429:
exception = new DigitalOcean2RateLimitExceededException(response, exception);
break;
}
command.setException(exception);
}
}

View File

@ -0,0 +1,111 @@
/*
* 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.digitalocean2.handlers;
import static org.jclouds.Constants.PROPERTY_MAX_RETRIES;
import static org.jclouds.digitalocean2.config.DigitalOcean2Properties.MAX_RATE_LIMIT_WAIT;
import javax.annotation.Resource;
import javax.inject.Named;
import javax.inject.Singleton;
import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpResponse;
import org.jclouds.http.HttpRetryHandler;
import org.jclouds.logging.Logger;
import com.google.common.annotations.Beta;
import com.google.inject.Inject;
/**
* Retry handler that takes into account the DigitalOcean rate limit and delays
* the requests until they are known to succeed.
*/
@Beta
@Singleton
public class RateLimitRetryHandler implements HttpRetryHandler {
static final String RATE_LIMIT_RESET_HEADER = "RateLimit-Reset";
@Resource
protected Logger logger = Logger.NULL;
@Inject(optional = true)
@Named(PROPERTY_MAX_RETRIES)
private int retryCountLimit = 5;
@Inject(optional = true)
@Named(MAX_RATE_LIMIT_WAIT)
private int maxRateLimitWait = 120000;
@Override
public boolean shouldRetryRequest(final HttpCommand command, final HttpResponse response) {
command.incrementFailureCount();
// Do not retry client errors that are not rate limit errors
if (response.getStatusCode() != 429) {
return false;
} else if (!command.isReplayable()) {
logger.error("Cannot retry after rate limit error, command is not replayable: %1$s", command);
return false;
} else if (command.getFailureCount() > retryCountLimit) {
logger.error("Cannot retry after rate limit error, command has exceeded retry limit %1$d: %2$s",
retryCountLimit, command);
return false;
} else {
return delayRequestUntilAllowed(command, response);
}
}
private boolean delayRequestUntilAllowed(final HttpCommand command, final HttpResponse response) {
// The header is the Unix epoch time when the next request can be done
String epochForNextAvailableRequest = response.getFirstHeaderOrNull(RATE_LIMIT_RESET_HEADER);
if (epochForNextAvailableRequest == null) {
logger.error("Cannot retry after rate limit error, no retry information provided in the response");
return false;
}
long waitPeriod = millisUntilNextAvailableRequest(Long.parseLong(epochForNextAvailableRequest));
if (waitPeriod > 0) {
if (waitPeriod > maxRateLimitWait) {
logger.error("Max wait for rate limited requests is %s seconds but need to wait %s seconds, aborting",
maxRateLimitWait, waitPeriod);
return false;
}
try {
logger.debug("Waiting %s seconds before retrying, as defined by the rate limit", waitPeriod);
// Do not use Uninterrumpibles or similar, to let the jclouds
// tiemout configuration interrupt this thread
Thread.sleep(waitPeriod);
} catch (InterruptedException ex) {
// If the request is being executed and has a timeout configured,
// the thread may be interrupted when the timeout is reached.
logger.error("Request execution was interrupted, aborting");
Thread.currentThread().interrupt();
return false;
}
}
return true;
}
public static long millisUntilNextAvailableRequest(long epochForNextAvailableRequest) {
return (epochForNextAvailableRequest * 1000) - System.currentTimeMillis();
}
}

View File

@ -0,0 +1,172 @@
/*
* 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.digitalocean2.ssh;
import static com.google.common.base.Joiner.on;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Splitter.fixedLength;
import static com.google.common.base.Throwables.propagate;
import static com.google.common.collect.Iterables.get;
import static com.google.common.collect.Iterables.size;
import static com.google.common.io.BaseEncoding.base16;
import static com.google.common.io.BaseEncoding.base64;
import static org.jclouds.util.Strings2.toStringAndClose;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.interfaces.DSAParams;
import java.security.interfaces.DSAPublicKey;
import java.security.spec.DSAPublicKeySpec;
import com.google.common.base.Charsets;
import com.google.common.base.Splitter;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hashing;
import com.google.common.io.ByteSource;
import com.google.common.io.ByteStreams;
/**
* Utility methods to work with DSA SSH keys.
* <p>
* Methods in this class should be moved to the {@link org.jclouds.ssh.SshKeys} class.
*
*
* @see org.jclouds.ssh.SshKeys
*/
public class DSAKeys {
public static String encodeAsOpenSSH(DSAPublicKey key) {
DSAParams params = key.getParams();
byte[] keyBlob = keyBlob(params.getP(), params.getQ(), params.getG(), key.getY());
return "ssh-dss " + base64().encode(keyBlob);
}
/**
* Executes {@link org.jclouds.crypto.Pems#publicKeySpecFromOpenSSH(com.google.common.io.InputSupplier)} on the
* string which was OpenSSH Base64 Encoded {@code id_rsa.pub}
*
* @param idRsaPub formatted {@code ssh-dss AAAAB3NzaC1yc2EAAAADAQABAAAB...}
* @see org.jclouds.crypto.Pems#publicKeySpecFromOpenSSH(com.google.common.io.InputSupplier)
*/
public static DSAPublicKeySpec publicKeySpecFromOpenSSH(String idDsaPub) {
try {
return publicKeySpecFromOpenSSH(ByteSource.wrap(idDsaPub.getBytes(Charsets.UTF_8)));
} catch (IOException e) {
throw propagate(e);
}
}
/**
* Returns {@link java.security.spec.DSAPublicKeySpec} which was OpenSSH Base64 Encoded {@code id_rsa.pub}
*
* @param supplier the input stream factory, formatted {@code ssh-dss AAAAB3NzaC1yc2EAAAADAQABAAAB...}
*
* @return the {@link java.security.spec.DSAPublicKeySpec} which was OpenSSH Base64 Encoded {@code id_rsa.pub}
* @throws java.io.IOException if an I/O error occurs
*/
public static DSAPublicKeySpec publicKeySpecFromOpenSSH(ByteSource supplier) throws IOException {
InputStream stream = supplier.openStream();
Iterable<String> parts = Splitter.on(' ').split(toStringAndClose(stream).trim());
checkArgument(size(parts) >= 2 && "ssh-dss".equals(get(parts, 0)), "bad format, should be: ssh-dss AAAAB3...");
stream = new ByteArrayInputStream(base64().decode(get(parts, 1)));
String marker = new String(readLengthFirst(stream));
checkArgument("ssh-dss".equals(marker), "looking for marker ssh-dss but got %s", marker);
BigInteger p = new BigInteger(readLengthFirst(stream));
BigInteger q = new BigInteger(readLengthFirst(stream));
BigInteger g = new BigInteger(readLengthFirst(stream));
BigInteger y = new BigInteger(readLengthFirst(stream));
return new DSAPublicKeySpec(y, p, q, g);
}
/**
* @param publicKeyOpenSSH RSA public key in OpenSSH format
* @return fingerprint ex. {@code 2b:a9:62:95:5b:8b:1d:61:e0:92:f7:03:10:e9:db:d9}
*/
public static String fingerprintPublicKey(String publicKeyOpenSSH) {
DSAPublicKeySpec publicKeySpec = publicKeySpecFromOpenSSH(publicKeyOpenSSH);
return fingerprint(publicKeySpec.getP(), publicKeySpec.getQ(), publicKeySpec.getG(), publicKeySpec.getY());
}
/**
* Create a fingerprint per the following <a href="http://tools.ietf.org/html/draft-friedl-secsh-fingerprint-00"
* >spec</a>
*
* @return hex fingerprint ex. {@code 2b:a9:62:95:5b:8b:1d:61:e0:92:f7:03:10:e9:db:d9}
*/
public static String fingerprint(BigInteger p, BigInteger q, BigInteger g, BigInteger y) {
byte[] keyBlob = keyBlob(p, q, g, y);
return hexColonDelimited(Hashing.md5().hashBytes(keyBlob));
}
/**
* @see org.jclouds.ssh.SshKeys
*/
private static String hexColonDelimited(HashCode hc) {
return on(':').join(fixedLength(2).split(base16().lowerCase().encode(hc.asBytes())));
}
/**
* @see org.jclouds.ssh.SshKeys
*/
private static byte[] keyBlob(BigInteger p, BigInteger q, BigInteger g, BigInteger y) {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
writeLengthFirst("ssh-dss".getBytes(), out);
writeLengthFirst(p.toByteArray(), out);
writeLengthFirst(q.toByteArray(), out);
writeLengthFirst(g.toByteArray(), out);
writeLengthFirst(y.toByteArray(), out);
return out.toByteArray();
} catch (IOException e) {
throw propagate(e);
}
}
/**
* @see org.jclouds.ssh.SshKeys
*/
// http://www.ietf.org/rfc/rfc4253.txt
private static byte[] readLengthFirst(InputStream in) throws IOException {
int byte1 = in.read();
int byte2 = in.read();
int byte3 = in.read();
int byte4 = in.read();
int length = (byte1 << 24) + (byte2 << 16) + (byte3 << 8) + (byte4 << 0);
byte[] val = new byte[length];
ByteStreams.readFully(in, val);
return val;
}
/**
* @see org.jclouds.ssh.SshKeys
*/
// http://www.ietf.org/rfc/rfc4253.txt
private static void writeLengthFirst(byte[] array, ByteArrayOutputStream out) throws IOException {
out.write(array.length >>> 24 & 0xFF);
out.write(array.length >>> 16 & 0xFF);
out.write(array.length >>> 8 & 0xFF);
out.write(array.length >>> 0 & 0xFF);
if (array.length == 1 && array[0] == (byte) 0x00) {
out.write(new byte[0]);
} else {
out.write(array);
}
}
}

View File

@ -0,0 +1,343 @@
/*
* 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.digitalocean2.ssh;
import static com.google.common.base.Joiner.on;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Splitter.fixedLength;
import static com.google.common.base.Throwables.propagate;
import static com.google.common.collect.Iterables.get;
import static com.google.common.collect.Iterables.size;
import static com.google.common.io.BaseEncoding.base16;
import static com.google.common.io.BaseEncoding.base64;
import static org.jclouds.util.Strings2.toStringAndClose;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECFieldFp;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.EllipticCurve;
import java.security.spec.InvalidKeySpecException;
import java.util.Map;
import java.util.TreeMap;
import com.google.common.base.Charsets;
import com.google.common.base.Splitter;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hashing;
import com.google.common.io.ByteSource;
import com.google.common.io.ByteStreams;
/**
* Utility methods to work with ECDSA Elliptic Curve DSA keys.
* <p>
* Methods in this class should be moved to the {@link org.jclouds.ssh.SshKeys} class.
*
* @see org.jclouds.ssh.SshKeys
*/
public class ECDSAKeys {
public static final String ECDSA_SHA2_PREFIX = "ecdsa-sha2-";
private static final String NISTP256 = "nistp256";
private static final String NISTP384 = "nistp384";
private static final String NISTP521 = "nistp521";
private static final Map<String, ECParameterSpec> CURVES = new TreeMap<String, ECParameterSpec>();
static {
CURVES.put(NISTP256, EllipticCurves.nistp256);
CURVES.put(NISTP384, EllipticCurves.nistp384);
CURVES.put(NISTP521, EllipticCurves.nistp521);
}
private static final Map<Integer, String> CURVE_SIZES = new TreeMap<Integer, String>();
static {
CURVE_SIZES.put(256, NISTP256);
CURVE_SIZES.put(384, NISTP384);
CURVE_SIZES.put(521, NISTP521);
}
public static String encodeAsOpenSSH(ECPublicKey key) {
String curveName = null;
try {
curveName = getCurveName(key.getParams());
} catch (IOException e) {
propagate(e);
}
String keyFormat = ECDSA_SHA2_PREFIX + curveName;
byte[] keyBlob = keyBlob(key);
return keyFormat + " " + base64().encode(keyBlob);
}
/**
* Executes {@link org.jclouds.crypto.Pems#publicKeySpecFromOpenSSH(com.google.common.io.InputSupplier)} on the
* string which was OpenSSH Base64 Encoded {@code id_rsa.pub}
*
* @param idRsaPub formatted {@code ssh-dss AAAAB3NzaC1yc2EAAAADAQABAAAB...}
* @see org.jclouds.crypto.Pems#publicKeySpecFromOpenSSH(com.google.common.io.InputSupplier)
*/
public static ECPublicKeySpec publicKeySpecFromOpenSSH(String ecDsaPub) {
try {
return publicKeySpecFromOpenSSH(ByteSource.wrap(ecDsaPub.getBytes(Charsets.UTF_8)));
} catch (IOException e) {
throw propagate(e);
}
}
/**
* Returns {@link java.security.spec.DSAPublicKeySpec} which was OpenSSH Base64 Encoded {@code id_rsa.pub}
*
* @param supplier the input stream factory, formatted {@code ssh-dss AAAAB3NzaC1yc2EAAAADAQABAAAB...}
*
* @return the {@link java.security.spec.DSAPublicKeySpec} which was OpenSSH Base64 Encoded {@code id_rsa.pub}
* @throws java.io.IOException if an I/O error occurs
*/
public static ECPublicKeySpec publicKeySpecFromOpenSSH(ByteSource supplier) throws IOException {
InputStream stream = supplier.openStream();
Iterable<String> parts = Splitter.on(' ').split(toStringAndClose(stream).trim());
String signatureFormat = get(parts, 0);
checkArgument(size(parts) >= 2 && signatureFormat.startsWith(ECDSA_SHA2_PREFIX), "bad format, should be: ecdsa-sha2-xxx AAAAB3...");
String curveName = signatureFormat.substring(ECDSA_SHA2_PREFIX.length());
if (!CURVES.containsKey(curveName)) {
throw new IOException("Unsupported curve: " + curveName);
}
ECParameterSpec spec = CURVES.get(curveName);
stream = new ByteArrayInputStream(base64().decode(get(parts, 1)));
@SuppressWarnings("unused")
String keyType = new String(readLengthFirst(stream));
String curveMarker = new String(readLengthFirst(stream));
checkArgument(curveName.equals(curveMarker), "looking for marker %s but got %s", curveName, curveMarker);
ECPoint ecPoint = decodeECPoint(readLengthFirst(stream), spec.getCurve());
return new ECPublicKeySpec(ecPoint, spec);
}
/**
* @param publicKeyOpenSSH RSA public key in OpenSSH format
* @return fingerprint ex. {@code 2b:a9:62:95:5b:8b:1d:61:e0:92:f7:03:10:e9:db:d9}
*/
public static String fingerprintPublicKey(String publicKeyOpenSSH) throws IOException {
ECPublicKeySpec publicKeySpec = publicKeySpecFromOpenSSH(publicKeyOpenSSH);
String fingerprint = null;
try {
ECPublicKey pk = (ECPublicKey) KeyFactory.getInstance("EC").generatePublic(publicKeySpec);
fingerprint = fingerprint(pk);
} catch (InvalidKeySpecException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return fingerprint;
}
/**
* Create a fingerprint per the following <a href="http://tools.ietf.org/html/draft-friedl-secsh-fingerprint-00"
* >spec</a>
*
* @return hex fingerprint ex. {@code 2b:a9:62:95:5b:8b:1d:61:e0:92:f7:03:10:e9:db:d9}
*/
public static String fingerprint(ECPublicKey publicKey) {
byte[] keyBlob = keyBlob(publicKey);
return hexColonDelimited(Hashing.md5().hashBytes(keyBlob));
}
/**
* @see org.jclouds.ssh.SshKeys
*/
private static String hexColonDelimited(HashCode hc) {
return on(':').join(fixedLength(2).split(base16().lowerCase().encode(hc.asBytes())));
}
private static byte[] keyBlob(ECPublicKey key) {
try {
String curveName = getCurveName(key.getParams());
ByteArrayOutputStream out = new ByteArrayOutputStream();
writeLengthFirst((ECDSA_SHA2_PREFIX + curveName).getBytes(), out);
writeLengthFirst(curveName.getBytes(), out);
writeLengthFirst(encodeECPoint(key.getW(), key.getParams().getCurve()), out);
return out.toByteArray();
} catch (IOException e) {
throw propagate(e);
}
}
/**
* @see org.jclouds.ssh.SshKeys
*/
// http://www.ietf.org/rfc/rfc4253.txt
private static byte[] readLengthFirst(InputStream in) throws IOException {
int byte1 = in.read();
int byte2 = in.read();
int byte3 = in.read();
int byte4 = in.read();
int length = (byte1 << 24) + (byte2 << 16) + (byte3 << 8) + (byte4 << 0);
byte[] val = new byte[length];
ByteStreams.readFully(in, val);
return val;
}
/**
* @see org.jclouds.ssh.SshKeys
*/
// http://www.ietf.org/rfc/rfc4253.txt
private static void writeLengthFirst(byte[] array, ByteArrayOutputStream out) throws IOException {
out.write(array.length >>> 24 & 0xFF);
out.write(array.length >>> 16 & 0xFF);
out.write(array.length >>> 8 & 0xFF);
out.write(array.length >>> 0 & 0xFF);
if (array.length == 1 && array[0] == (byte) 0x00) {
out.write(new byte[0]);
} else {
out.write(array);
}
}
private static String getCurveName(ECParameterSpec params) throws IOException {
int fieldSize = getCurveSize(params);
String curveName = CURVE_SIZES.get(fieldSize);
if (curveName == null) {
throw new IOException("Unsupported curve field size: " + fieldSize);
}
return curveName;
}
private static int getCurveSize(ECParameterSpec params) {
return params.getCurve().getField().getFieldSize();
}
/**
* Encode EllipticCurvePoint to an OctetString
*/
public static byte[] encodeECPoint(ECPoint group, EllipticCurve curve)
{
// M has len 2 ceil(log_2(q)/8) + 1 ?
int elementSize = (curve.getField().getFieldSize() + 7) / 8;
byte[] M = new byte[2 * elementSize + 1];
// Uncompressed format
M[0] = 0x04;
{
byte[] affineX = removeLeadingZeroes(group.getAffineX().toByteArray());
System.arraycopy(affineX, 0, M, 1 + elementSize - affineX.length, affineX.length);
}
{
byte[] affineY = removeLeadingZeroes(group.getAffineY().toByteArray());
System.arraycopy(affineY, 0, M, 1 + elementSize + elementSize - affineY.length,
affineY.length);
}
return M;
}
private static byte[] removeLeadingZeroes(byte[] input) {
if (input[0] != 0x00) {
return input;
}
int pos = 1;
while (pos < input.length - 1 && input[pos] == 0x00) {
pos++;
}
byte[] output = new byte[input.length - pos];
System.arraycopy(input, pos, output, 0, output.length);
return output;
}
/**
* Decode an OctetString to EllipticCurvePoint according to SECG 2.3.4
*/
public static ECPoint decodeECPoint(byte[] M, EllipticCurve curve) {
if (M.length == 0) {
return null;
}
// M has len 2 ceil(log_2(q)/8) + 1 ?
int elementSize = (curve.getField().getFieldSize() + 7) / 8;
if (M.length != 2 * elementSize + 1) {
return null;
}
// step 3.2
if (M[0] != 0x04) {
return null;
}
// Step 3.3
byte[] xp = new byte[elementSize];
System.arraycopy(M, 1, xp, 0, elementSize);
// Step 3.4
byte[] yp = new byte[elementSize];
System.arraycopy(M, 1 + elementSize, yp, 0, elementSize);
ECPoint P = new ECPoint(new BigInteger(1, xp), new BigInteger(1, yp));
// TODO check point 3.5
// Step 3.6
return P;
}
public static class EllipticCurves {
public static ECParameterSpec nistp256 = new ECParameterSpec(
new EllipticCurve(
new ECFieldFp(new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", 16)),
new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", 16),
new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16)),
new ECPoint(new BigInteger("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296", 16),
new BigInteger("4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5", 16)),
new BigInteger("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", 16),
1);
public static ECParameterSpec nistp384 = new ECParameterSpec(
new EllipticCurve(
new ECFieldFp(new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF", 16)),
new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC", 16),
new BigInteger("B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF", 16)),
new ECPoint(new BigInteger("AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7", 16),
new BigInteger("3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F", 16)),
new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973", 16),
1);
public static ECParameterSpec nistp521 = new ECParameterSpec(
new EllipticCurve(
new ECFieldFp(new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16)),
new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC", 16),
new BigInteger("0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00", 16)
),
new ECPoint(new BigInteger("00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66", 16),
new BigInteger("011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE72995EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650", 16)
),
new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409", 16),
1);
}
}

View File

@ -0,0 +1,29 @@
/*
* 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.digitalocean2;
import org.jclouds.providers.internal.BaseProviderMetadataTest;
import org.testng.annotations.Test;
@Test(groups = "unit", testName = "DigitalOcean2ProviderMetadataTest")
public class DigitalOcean2ProviderMetadataTest extends BaseProviderMetadataTest {
public DigitalOcean2ProviderMetadataTest() {
super(new DigitalOcean2ProviderMetadata(), new DigitalOcean2ApiMetadata());
}
}

View File

@ -0,0 +1,66 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.digitalocean2.compute;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.internal.BaseComputeServiceLiveTest;
import org.jclouds.digitalocean2.config.DigitalOcean2RateLimitModule;
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 = "DigitalOcean2ComputeServiceLiveTest")
public class DigitalOcean2ComputeServiceLiveTest extends BaseComputeServiceLiveTest {
public DigitalOcean2ComputeServiceLiveTest() {
provider = "digitalocean2";
}
@Override
protected Module getSshModule() {
return new SshjSshClientModule();
}
@Override
protected Iterable<Module> setupModules() {
return ImmutableSet.<Module> builder().addAll(super.setupModules()).add(new DigitalOcean2RateLimitModule())
.build();
}
@Override
public void testOptionToNotBlock() throws Exception {
// DigitalOcean ComputeService implementation has to block until the node
// is provisioned, to be able to return it.
}
@Override
protected void checkTagsInNodeEquals(NodeMetadata node, ImmutableSet<String> tags) {
// We encode the tags in the user data but the DigitalOcean API does not return it
}
@Override
protected void checkUserMetadataContains(NodeMetadata node, ImmutableMap<String, String> userMetadata) {
// The DigitalOcean API does not return the user data
}
}

View File

@ -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.digitalocean2.compute;
import static org.jclouds.compute.util.ComputeServiceUtils.getCores;
import static org.testng.Assert.assertEquals;
import java.io.IOException;
import java.util.Set;
import org.jclouds.compute.domain.OsFamily;
import org.jclouds.compute.domain.Template;
import org.jclouds.compute.internal.BaseTemplateBuilderLiveTest;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableSet;
@Test(groups = "live", testName = "DigitalOcean2TemplateBuilderLiveTest")
public class DigitalOcean2TemplateBuilderLiveTest extends BaseTemplateBuilderLiveTest {
public DigitalOcean2TemplateBuilderLiveTest() {
provider = "digitalocean2";
}
@Test
@Override
public void testDefaultTemplateBuilder() throws IOException {
Template defaultTemplate = view.getComputeService().templateBuilder().build();
assert defaultTemplate.getImage().getOperatingSystem().getVersion().equals("15.10") : 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.<String> of();
}
}

View File

@ -0,0 +1,74 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.digitalocean2.compute.config;
import static org.easymock.EasyMock.anyInt;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
import java.util.Date;
import org.easymock.EasyMock;
import org.jclouds.digitalocean2.DigitalOcean2Api;
import org.jclouds.digitalocean2.compute.config.DigitalOcean2ComputeServiceContextModule.ActionDonePredicate;
import org.jclouds.digitalocean2.domain.Action;
import org.jclouds.digitalocean2.features.ActionApi;
import org.testng.annotations.Test;
@Test(groups = "unit", testName = "ActionDonePredicateTest")
public class ActionDonePredicateTest {
public void testActionStatusOk() {
ActionApi actionApi = EasyMock.createMock(ActionApi.class);
DigitalOcean2Api api = EasyMock.createMock(DigitalOcean2Api.class);
expect(actionApi.get(1)).andReturn(action(Action.Status.COMPLETED));
expect(actionApi.get(2)).andReturn(action(Action.Status.IN_PROGRESS));
expect(api.actionApi()).andReturn(actionApi).times(2);
replay(actionApi, api);
ActionDonePredicate predicate = new ActionDonePredicate(api);
assertTrue(predicate.apply(1));
assertFalse(predicate.apply(2));
}
public void testActionStatusError() {
ActionApi actionApi = EasyMock.createMock(ActionApi.class);
DigitalOcean2Api api = EasyMock.createMock(DigitalOcean2Api.class);
expect(actionApi.get(anyInt())).andReturn(action(Action.Status.ERRORED));
expect(api.actionApi()).andReturn(actionApi);
replay(actionApi, api);
ActionDonePredicate predicate = new ActionDonePredicate(api);
try {
predicate.apply(1);
fail("Method should have thrown an IllegalStateException");
} catch (IllegalStateException ex) {
assertEquals(ex.getMessage(), "Resource is in invalid status: ERRORED");
}
}
private static Action action(Action.Status status) {
return Action.create(1, status, "foo", new Date(), new Date(), 1, "", null, "");
}
}

View File

@ -0,0 +1,58 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.digitalocean2.compute.config;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import java.util.Date;
import org.easymock.EasyMock;
import org.jclouds.digitalocean2.DigitalOcean2Api;
import org.jclouds.digitalocean2.compute.config.DigitalOcean2ComputeServiceContextModule.DropletInStatusPredicate;
import org.jclouds.digitalocean2.domain.Droplet;
import org.jclouds.digitalocean2.domain.Droplet.Status;
import org.jclouds.digitalocean2.features.DropletApi;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
@Test(groups = "unit", testName = "DropletInStatusPredicateTest")
public class DropletInStatusPredicateTest {
public void testDropletSuspended() {
DropletApi dropletApi = EasyMock.createMock(DropletApi.class);
DigitalOcean2Api api = EasyMock.createMock(DigitalOcean2Api.class);
expect(dropletApi.get(1)).andReturn(mockDroplet(Status.ACTIVE));
expect(dropletApi.get(2)).andReturn(mockDroplet(Status.OFF));
expect(api.dropletApi()).andReturn(dropletApi).times(2);
replay(dropletApi, api);
DropletInStatusPredicate predicate = new DropletInStatusPredicate(api, Status.OFF);
assertFalse(predicate.apply(1));
assertTrue(predicate.apply(2));
}
private static Droplet mockDroplet(Status status) {
return Droplet.create(1, "foo", 1024, 1, 20, false, new Date(), status,
ImmutableList.<Integer> of(), ImmutableList.<Integer> of(), ImmutableList.<String> of(), null, null, null,
"", null, null);
}
}

View File

@ -0,0 +1,57 @@
/*
* 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.digitalocean2.compute.config;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import java.util.Date;
import org.easymock.EasyMock;
import org.jclouds.digitalocean2.DigitalOcean2Api;
import org.jclouds.digitalocean2.compute.config.DigitalOcean2ComputeServiceContextModule.DropletTerminatedPredicate;
import org.jclouds.digitalocean2.domain.Droplet;
import org.jclouds.digitalocean2.features.DropletApi;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
@Test(groups = "unit", testName = "DropletTerminatedPredicateTest")
public class DropletTerminatedPredicateTest {
public void testDropletTerminated() {
DropletApi dropletApi = EasyMock.createMock(DropletApi.class);
DigitalOcean2Api api = EasyMock.createMock(DigitalOcean2Api.class);
expect(dropletApi.get(1)).andReturn(mockDroplet());
expect(dropletApi.get(2)).andReturn(null);
expect(api.dropletApi()).andReturn(dropletApi).times(2);
replay(dropletApi, api);
DropletTerminatedPredicate predicate = new DropletTerminatedPredicate(api);
assertFalse(predicate.apply(1));
assertTrue(predicate.apply(2));
}
private static Droplet mockDroplet() {
return Droplet.create(1, "foo", 1024, 1, 20, false, new Date(), Droplet.Status.ACTIVE,
ImmutableList.<Integer> of(), ImmutableList.<Integer> of(), ImmutableList.<String> of(), null, null, null,
"", null, null);
}
}

View File

@ -0,0 +1,40 @@
/*
* 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.digitalocean2.compute.extensions;
import org.jclouds.compute.extensions.internal.BaseImageExtensionLiveTest;
import org.jclouds.sshj.config.SshjSshClientModule;
import org.testng.annotations.Test;
import com.google.inject.Module;
/**
* Live tests for the {@link org.jclouds.compute.extensions.ImageExtension} integration.
*/
@Test(groups = "live", singleThreaded = true, testName = "DigitalOcean2ImageExtensionLiveTest")
public class DigitalOcean2ImageExtensionLiveTest extends BaseImageExtensionLiveTest {
public DigitalOcean2ImageExtensionLiveTest() {
provider = "digitalocean2";
}
@Override
protected Module getSshModule() {
return new SshjSshClientModule();
}
}

View File

@ -0,0 +1,36 @@
/*
* 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.digitalocean2.compute.functions;
import static org.jclouds.compute.domain.NodeMetadata.Status.UNRECOGNIZED;
import static org.testng.Assert.assertNotEquals;
import org.jclouds.digitalocean2.domain.Droplet.Status;
import org.testng.annotations.Test;
@Test(groups = "unit", testName = "DropletStatusToStatusTest")
public class DropletStatusToStatusTest {
@Test
public void testAllStatesHaveMapping() {
DropletStatusToStatus function = new DropletStatusToStatus();
for (Status status : Status.values()) {
assertNotEquals(function.apply(status), UNRECOGNIZED);
}
}
}

View File

@ -0,0 +1,237 @@
/*
* 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.digitalocean2.compute.functions;
import static com.google.common.collect.Iterables.getOnlyElement;
import static org.jclouds.compute.domain.Image.Status.AVAILABLE;
import static org.jclouds.compute.domain.NodeMetadata.Status.RUNNING;
import static org.jclouds.digitalocean2.domain.Droplet.Status.ACTIVE;
import static org.testng.Assert.assertEquals;
import java.text.ParseException;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import org.jclouds.compute.domain.Hardware;
import org.jclouds.compute.domain.HardwareBuilder;
import org.jclouds.compute.domain.Image;
import org.jclouds.compute.domain.ImageBuilder;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.domain.NodeMetadataBuilder;
import org.jclouds.compute.domain.OperatingSystem;
import org.jclouds.compute.domain.OsFamily;
import org.jclouds.compute.domain.Processor;
import org.jclouds.compute.domain.Volume.Type;
import org.jclouds.compute.domain.VolumeBuilder;
import org.jclouds.compute.functions.GroupNamingConvention;
import org.jclouds.digitalocean2.domain.Droplet;
import org.jclouds.digitalocean2.domain.Networks;
import org.jclouds.digitalocean2.domain.Networks.Address;
import org.jclouds.digitalocean2.domain.Region;
import org.jclouds.domain.Credentials;
import org.jclouds.domain.Location;
import org.jclouds.domain.LocationBuilder;
import org.jclouds.domain.LocationScope;
import org.jclouds.domain.LoginCredentials;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.google.common.base.Function;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.inject.Guice;
@Test(groups = "unit", testName = "DropletToNodeMetadataTest")
public class DropletToNodeMetadataTest {
private org.jclouds.digitalocean2.domain.Image image;
private Region region;
private Set<Hardware> hardwares;
private Set<Image> images;
private Set<Location> locations;
private LoginCredentials credentials;
private DropletToNodeMetadata function;
@BeforeMethod
public void setup() {
image = org.jclouds.digitalocean2.domain.Image.create(1, "14.04 x64",
"distribution", "Ubuntu", "ubuntu-1404-x86", true, ImmutableList.of("sfo1"), new Date());
region = Region.create("sfo1", "San Francisco 1", ImmutableList.of("2gb"), true, ImmutableList.<String> of());
images = ImmutableSet.of(new ImageBuilder()
.id("sfo1/ubuntu-1404-x86")
.providerId("1")
.name("mock image")
.status(AVAILABLE)
.operatingSystem(
OperatingSystem.builder().name("Ubuntu 14.04 x86_64").description("Ubuntu").family(OsFamily.UBUNTU)
.version("10.04").arch("x86_64").is64Bit(true).build()).build());
hardwares = ImmutableSet.of(new HardwareBuilder().id("2gb").providerId("2gb").name("mock hardware")
.processor(new Processor(1.0, 1.0)).ram(2048)
.volume(new VolumeBuilder().size(20f).type(Type.LOCAL).build()).build());
locations = ImmutableSet.of(new LocationBuilder()
.id("sfo1")
.description("sfo1/San Francisco 1")
.scope(LocationScope.REGION)
.parent(
new LocationBuilder().id("0").description("mock parent location").scope(LocationScope.PROVIDER)
.build()).build());
credentials = LoginCredentials.builder().user("foo").password("bar").build();
function = createNodeParser(hardwares, images, locations, ImmutableMap.of("node#1", (Credentials) credentials));
}
@Test
public void testConvertDroplet() throws ParseException {
Droplet droplet = Droplet.create(
1,
"mock-droplet",
1,
1,
1,
false,
new Date(),
Droplet.Status.ACTIVE,
ImmutableList.<Integer> of(),
ImmutableList.<Integer> of(),
ImmutableList.<String> of(),
region,
image,
null,
"2gb",
Networks.create(
ImmutableList.of(Address.create("84.45.69.3", "255.255.255.0", "84.45.69.1", "public"),
Address.create("192.168.2.5", "255.255.255.0", "192.168.2.1", "private")),
ImmutableList.<Networks.Address> of()), null);
NodeMetadata expected = new NodeMetadataBuilder().ids("1").hardware(getOnlyElement(hardwares))
.imageId("sfo1/ubuntu-1404-x86").status(RUNNING).location(getOnlyElement(locations)).name("mock-droplet")
.hostname("mock-droplet").group("mock").credentials(credentials)
.publicAddresses(ImmutableSet.of("84.45.69.3")).privateAddresses(ImmutableSet.of("192.168.2.5"))
.providerId("1").backendStatus(ACTIVE.name()).operatingSystem(getOnlyElement(images).getOperatingSystem())
.build();
NodeMetadata actual = function.apply(droplet);
assertNodeEquals(actual, expected);
}
@Test
public void testConvertDropletOldImage() throws ParseException {
// Use an image id that is not in the list of images
org.jclouds.digitalocean2.domain.Image image = org.jclouds.digitalocean2.domain.Image.create(2, "14.04 x64",
"distribution", "Ubuntu", "ubuntu2-1404-x86", true, ImmutableList.of("sfo1"), new Date());
Droplet droplet = Droplet.create(
1,
"mock-droplet",
1,
1,
1,
false,
new Date(),
Droplet.Status.ACTIVE,
ImmutableList.<Integer> of(),
ImmutableList.<Integer> of(),
ImmutableList.<String> of(),
region,
image,
null,
"2gb",
Networks.create(
ImmutableList.of(Address.create("84.45.69.3", "255.255.255.0", "84.45.69.1", "public"),
Address.create("192.168.2.5", "255.255.255.0", "192.168.2.1", "private")),
ImmutableList.<Networks.Address> of()), null);
NodeMetadata expected = new NodeMetadataBuilder().ids("1").hardware(getOnlyElement(hardwares)).imageId(null)
.status(RUNNING).location(getOnlyElement(locations)).name("mock-droplet").hostname("mock-droplet")
.group("mock").credentials(credentials).publicAddresses(ImmutableSet.of("84.45.69.3"))
.privateAddresses(ImmutableSet.of("192.168.2.5")).providerId("1").backendStatus(ACTIVE.name())
.operatingSystem(null).build();
NodeMetadata actual = function.apply(droplet);
assertNodeEquals(actual, expected);
}
private static void assertNodeEquals(NodeMetadata actual, NodeMetadata expected) {
assertEquals(actual, expected);
// NodeMetadata equals method does not use all fields in equals. It assumes that same ids in same locations
// determine the equivalence
assertEquals(actual.getStatus(), expected.getStatus());
assertEquals(actual.getBackendStatus(), expected.getBackendStatus());
assertEquals(actual.getLoginPort(), expected.getLoginPort());
assertEquals(actual.getPublicAddresses(), expected.getPublicAddresses());
assertEquals(actual.getPrivateAddresses(), expected.getPrivateAddresses());
assertEquals(actual.getCredentials(), expected.getCredentials());
assertEquals(actual.getGroup(), expected.getGroup());
assertEquals(actual.getImageId(), expected.getImageId());
assertEquals(actual.getHardware(), expected.getHardware());
assertEquals(actual.getOperatingSystem(), expected.getOperatingSystem());
assertEquals(actual.getHostname(), expected.getHostname());
}
private DropletToNodeMetadata createNodeParser(final Set<Hardware> hardware, final Set<Image> images,
final Set<Location> locations, Map<String, Credentials> credentialStore) {
Supplier<Set<? extends Location>> locationSupplier = new Supplier<Set<? extends Location>>() {
@Override
public Set<? extends Location> get() {
return locations;
}
};
Supplier<Map<String, ? extends Hardware>> hardwareSupplier = new Supplier<Map<String, ? extends Hardware>>() {
@Override
public Map<String, ? extends Hardware> get() {
return Maps.uniqueIndex(hardware, new Function<Hardware, String>() {
@Override
public String apply(Hardware input) {
return input.getId();
}
});
}
};
Supplier<Map<String, ? extends Image>> imageSupplier = new Supplier<Map<String, ? extends Image>>() {
@Override
public Map<String, ? extends Image> get() {
return Maps.uniqueIndex(images, new Function<Image, String>() {
@Override
public String apply(Image input) {
return input.getId();
}
});
}
};
GroupNamingConvention.Factory namingConvention = Guice.createInjector().getInstance(GroupNamingConvention.Factory.class);
return new DropletToNodeMetadata(imageSupplier, hardwareSupplier, locationSupplier, new DropletStatusToStatus(),
namingConvention, credentialStore);
}
}

View File

@ -0,0 +1,98 @@
/*
* 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.digitalocean2.compute.functions;
import static org.jclouds.compute.domain.Image.Status.AVAILABLE;
import static org.testng.Assert.assertEquals;
import java.util.Date;
import java.util.Set;
import org.jclouds.compute.domain.ImageBuilder;
import org.jclouds.compute.domain.OperatingSystem;
import org.jclouds.compute.domain.OsFamily;
import org.jclouds.digitalocean2.compute.internal.ImageInRegion;
import org.jclouds.digitalocean2.domain.Image;
import org.jclouds.domain.Location;
import org.jclouds.domain.LocationBuilder;
import org.jclouds.domain.LocationScope;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
@Test(groups = "unit", testName = "ImageToImageTest")
public class ImageInRegionToImageTest {
private Set<Location> locations;
private ImageInRegionToImage function;
@BeforeMethod
public void setup() {
locations = ImmutableSet.of(
new LocationBuilder()
.id("sfo1")
.description("sfo1/San Francisco 1")
.scope(LocationScope.REGION)
.parent(
new LocationBuilder().id("0").description("mock parent location").scope(LocationScope.PROVIDER)
.build()).build(),
new LocationBuilder()
.id("lon1")
.description("lon1/London 1")
.scope(LocationScope.REGION)
.parent(
new LocationBuilder().id("0").description("mock parent location").scope(LocationScope.PROVIDER)
.build()).build());
function = new ImageInRegionToImage(new Supplier<Set<? extends Location>>() {
@Override
public Set<? extends Location> get() {
return locations;
}
});
}
@Test
public void testConvertImage() {
Image image = Image.create(1, "14.04 x64", "distribution", "Ubuntu", "ubuntu-1404-x86", true,
ImmutableList.of("sfo1", "lon1"), new Date());
org.jclouds.compute.domain.Image expected = new ImageBuilder()
.id("lon1/ubuntu-1404-x86") // Location scoped images have the location encoded in the id
.providerId("1")
.name("14.04 x64")
.description("Ubuntu 14.04 x64")
.status(AVAILABLE)
.operatingSystem(
OperatingSystem.builder().name("Ubuntu").description("Ubuntu 14.04 x64").family(OsFamily.UBUNTU)
.version("14.04").arch("x64").is64Bit(true).build())
.location(Iterables.get(locations, 1))
.userMetadata(ImmutableMap.of("publicImage", "true")).build();
org.jclouds.compute.domain.Image result = function.apply(ImageInRegion.create(image, "lon1"));
assertEquals(result, expected);
assertEquals(result.getDescription(), expected.getDescription());
assertEquals(result.getOperatingSystem(), expected.getOperatingSystem());
assertEquals(result.getStatus(), expected.getStatus());
assertEquals(result.getLocation(), Iterables.get(locations, 1));
}
}

View File

@ -0,0 +1,56 @@
/*
* 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.digitalocean2.compute.functions;
import static com.google.common.collect.Iterables.getOnlyElement;
import static org.testng.Assert.assertEquals;
import java.net.URI;
import org.jclouds.digitalocean2.DigitalOcean2ProviderMetadata;
import org.jclouds.digitalocean2.domain.Region;
import org.jclouds.domain.Location;
import org.jclouds.domain.LocationBuilder;
import org.jclouds.domain.LocationScope;
import org.jclouds.location.suppliers.all.JustProvider;
import org.testng.annotations.Test;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@Test(groups = "unit", testName = "RegionToLocationTest")
public class RegionToLocationTest {
@Test
public void testConvertRegion() {
DigitalOcean2ProviderMetadata metadata = new DigitalOcean2ProviderMetadata();
JustProvider locationsSupplier = new JustProvider(metadata.getId(), Suppliers.<URI> ofInstance(URI
.create(metadata.getEndpoint())), ImmutableSet.<String> of());
Region region = Region.create("reg1", "Region1", ImmutableList.<String> of(), true,
ImmutableList.<String> of("virtio", "metadata"));
Location expected = new LocationBuilder().id("reg1").description("reg1/Region 1")
.parent(getOnlyElement(locationsSupplier.get())).scope(LocationScope.REGION).build();
Location location = new RegionToLocation(locationsSupplier).apply(region);
assertEquals(location, expected);
assertEquals(location.getMetadata().get("available"), true);
assertEquals(location.getMetadata().get("features"), ImmutableList.of("virtio", "metadata"));
}
}

View File

@ -0,0 +1,49 @@
/*
* 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.digitalocean2.compute.functions;
import static org.testng.Assert.assertEquals;
import org.jclouds.compute.domain.Hardware;
import org.jclouds.compute.domain.HardwareBuilder;
import org.jclouds.compute.domain.Processor;
import org.jclouds.compute.domain.Volume.Type;
import org.jclouds.compute.domain.VolumeBuilder;
import org.jclouds.digitalocean2.domain.Size;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
/**
* Unit tests for the {@link SizeToHardware} class.
*/
@Test(groups = "unit", testName = "SizeToHardwareTest")
public class SizeToHardwareTest {
@Test
public void testConvertSize() {
Size size = Size.create("2gb", true, 1.0f, 10f, 0.05f, 2048, 1, 20, ImmutableList.<String> of());
Hardware expected = new HardwareBuilder().id("2gb").providerId("2gb").name("2gb")
.processor(new Processor(1.0, 1.0)).ram(2048)
.volume(new VolumeBuilder().size(20f).type(Type.LOCAL).build())
.userMetadata(ImmutableMap.of("costPerHour", "0.05", "costPerMonth", "10")).build();
SizeToHardware function = new SizeToHardware();
assertEquals(function.apply(size), expected);
}
}

View File

@ -0,0 +1,75 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.digitalocean2.compute.functions;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
import java.util.Map;
import org.jclouds.compute.options.TemplateOptions;
import org.jclouds.scriptbuilder.domain.OsFamily;
import org.jclouds.scriptbuilder.domain.Statement;
import org.jclouds.scriptbuilder.domain.StatementList;
import org.jclouds.scriptbuilder.statements.ssh.InstallRSAPrivateKey;
import org.jclouds.ssh.SshKeys;
import org.testng.annotations.Test;
/**
* Unit tests for the {@link TemplateOptionsToStatementWithoutPublicKey} class.
*/
@Test(groups = "unit", testName = "TemplateOptionsToStatementWithoutPublicKeyTest")
public class TemplateOptionsToStatementWithoutPublicKeyTest {
@Test
public void testPublicKeyDoesNotGenerateAuthorizePublicKeyStatementIfOnlyPublicKeyOptionsConfigured() {
Map<String, String> keys = SshKeys.generate();
TemplateOptions options = TemplateOptions.Builder.authorizePublicKey(keys.get("public"));
TemplateOptionsToStatementWithoutPublicKey function = new TemplateOptionsToStatementWithoutPublicKey();
assertNull(function.apply(options));
}
@Test
public void testPublicAndRunScriptKeyDoesNotGenerateAuthorizePublicKeyStatementIfRunScriptPresent() {
Map<String, String> keys = SshKeys.generate();
TemplateOptions options = TemplateOptions.Builder.authorizePublicKey(keys.get("public")).runScript("uptime");
TemplateOptionsToStatementWithoutPublicKey function = new TemplateOptionsToStatementWithoutPublicKey();
Statement statement = function.apply(options);
assertEquals(statement.render(OsFamily.UNIX), "uptime\n");
}
@Test
public void testPublicAndPrivateKeyAndRunScriptDoesNotGenerateAuthorizePublicKeyStatementIfOtherOptionsPresent() {
Map<String, String> keys = SshKeys.generate();
TemplateOptions options = TemplateOptions.Builder.authorizePublicKey(keys.get("public"))
.installPrivateKey(keys.get("private")).runScript("uptime");
TemplateOptionsToStatementWithoutPublicKey function = new TemplateOptionsToStatementWithoutPublicKey();
Statement statement = function.apply(options);
assertTrue(statement instanceof StatementList);
StatementList statements = (StatementList) statement;
assertEquals(statements.size(), 2);
assertEquals(statements.get(0).render(OsFamily.UNIX), "uptime\n");
assertTrue(statements.get(1) instanceof InstallRSAPrivateKey);
}
}

View File

@ -0,0 +1,52 @@
/*
* 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.digitalocean2.compute.options;
import static org.testng.Assert.assertEquals;
import org.jclouds.compute.options.TemplateOptions;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableSet;
@Test(groups = "unit", testName = "DigitalOcean2TemplateOptionsTest")
public class DigitalOcean2TemplateOptionsTest {
@Test
public void testSShKeyIds() {
TemplateOptions options = new DigitalOcean2TemplateOptions().sshKeyIds(ImmutableSet.of(1, 2, 3));
assertEquals(options.as(DigitalOcean2TemplateOptions.class).getSshKeyIds(), ImmutableSet.of(1, 2, 3));
}
@Test
public void testPrivateNetworking() {
TemplateOptions options = new DigitalOcean2TemplateOptions().privateNetworking(true);
assertEquals(options.as(DigitalOcean2TemplateOptions.class).getPrivateNetworking(), true);
}
@Test
public void testBackupsEnabled() {
TemplateOptions options = new DigitalOcean2TemplateOptions().backupsEnabled(true);
assertEquals(options.as(DigitalOcean2TemplateOptions.class).getBackupsEnabled(), true);
}
@Test
public void testAutoCreateKeyPair() {
TemplateOptions options = new DigitalOcean2TemplateOptions().autoCreateKeyPair(false);
assertEquals(options.as(DigitalOcean2TemplateOptions.class).getAutoCreateKeyPair(), false);
}
}

View File

@ -0,0 +1,104 @@
/*
* 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.digitalocean2.domain;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import org.testng.annotations.Test;
@Test(groups = "unit", testName = "OperatingSystemTest")
public class OperatingSystemTest {
public void testParseStandard64bit() {
OperatingSystem os = OperatingSystem.create("12.10 x64", "Ubuntu");
assertEquals(os.distribution(), Distribution.UBUNTU);
assertEquals(os.version(), "12.10");
assertEquals(os.arch(), "x64");
assertTrue(os.is64bit());
}
public void testLongVersionStandard64bit() {
OperatingSystem os = OperatingSystem.create("12.10.1 x64", "Ubuntu");
assertEquals(os.distribution(), Distribution.UBUNTU);
assertEquals(os.version(), "12.10.1");
assertEquals(os.arch(), "x64");
assertTrue(os.is64bit());
}
public void testParseStandard64bitWithPrefix() {
OperatingSystem os = OperatingSystem.create("Arch Linux 12.10 x64 Desktop", "Arch Linux");
assertEquals(os.distribution(), Distribution.ARCHLINUX);
assertEquals(os.version(), "12.10");
assertEquals(os.arch(), "x64");
assertTrue(os.is64bit());
}
public void testParseStandard() {
OperatingSystem os = OperatingSystem.create("12.10 x32", "Ubuntu");
assertEquals(os.distribution(), Distribution.UBUNTU);
assertEquals(os.version(), "12.10");
assertEquals(os.arch(), "x32");
assertFalse(os.is64bit());
os = OperatingSystem.create("6.5 x64", "CentOS");
assertEquals(os.distribution(), Distribution.CENTOS);
assertEquals(os.version(), "6.5");
assertEquals(os.arch(), "x64");
assertTrue(os.is64bit());
os = OperatingSystem.create("6.5 x64", "Centos");
assertEquals(os.distribution(), Distribution.CENTOS);
assertEquals(os.version(), "6.5");
assertEquals(os.arch(), "x64");
assertTrue(os.is64bit());
}
public void testParseNoArch() {
OperatingSystem os = OperatingSystem.create("12.10", "Ubuntu");
assertEquals(os.distribution(), Distribution.UBUNTU);
assertEquals(os.version(), "12.10");
assertEquals(os.arch(), "");
assertFalse(os.is64bit());
}
public void testParseNoVersion() {
OperatingSystem os = OperatingSystem.create("x64", "Ubuntu");
assertEquals(os.distribution(), Distribution.UBUNTU);
assertEquals(os.version(), "");
assertEquals(os.arch(), "x64");
assertTrue(os.is64bit());
}
public void testParseUnknownDistribution() {
OperatingSystem os = OperatingSystem.create("12.04 x64", "Foo");
assertEquals(os.distribution(), Distribution.UNRECOGNIZED);
assertEquals(os.version(), "12.04");
assertEquals(os.arch(), "x64");
assertTrue(os.is64bit());
}
}

View File

@ -0,0 +1,63 @@
/*
* 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.digitalocean2.exceptions;
import static org.jclouds.Constants.PROPERTY_MAX_RETRIES;
import static org.jclouds.digitalocean2.handlers.RateLimitRetryHandler.millisUntilNextAvailableRequest;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
import java.util.Properties;
import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiMockTest;
import org.testng.annotations.Test;
import com.squareup.okhttp.mockwebserver.MockResponse;
@Test(groups = "unit", testName = "RateLimitExceptionMockTest", singleThreaded = true)
public class RateLimitExceptionMockTest extends BaseDigitalOcean2ApiMockTest {
@Override
protected Properties overrides() {
Properties overrides = super.overrides();
overrides.put(PROPERTY_MAX_RETRIES, "0"); // Do not retry
return overrides;
}
public void testRateLimitExceptionIsThrown() throws InterruptedException {
long reset = (System.currentTimeMillis() / 1000) + 3600; // Epoch for one
// hour from now
long millisToReset = millisUntilNextAvailableRequest(reset);
server.enqueue(new MockResponse().setResponseCode(429).addHeader("RateLimit-Limit", "5000")
.addHeader("RateLimit-Remaining", "1235").addHeader("RateLimit-Reset", String.valueOf(reset)));
try {
api.keyApi().list();
fail("Expected a DigitalOcean2RateLimitExceededException to be thrown");
} catch (DigitalOcean2RateLimitExceededException ex) {
assertEquals(ex.totalRequestsPerHour().intValue(), 5000);
assertEquals(ex.remainingRequests().intValue(), 1235);
// Can't verify with millisecond precision. Use an interval to have a
// consistent test.
assertTrue(ex.timeToNextAvailableRequest() < millisToReset
&& ex.timeToNextAvailableRequest() > millisToReset - 1800000);
}
}
}

View File

@ -0,0 +1,70 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.digitalocean2.features;
import static org.jclouds.digitalocean2.domain.options.ImageListOptions.Builder.page;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import static org.testng.util.Strings.isNullOrEmpty;
import java.util.concurrent.atomic.AtomicInteger;
import org.jclouds.digitalocean2.domain.Action;
import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiLiveTest;
import org.testng.annotations.Test;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
@Test(groups = "live", testName = "ActionApiLiveTest")
public class ActionApiLiveTest extends BaseDigitalOcean2ApiLiveTest {
public void testListActions() {
final AtomicInteger found = new AtomicInteger(0);
// DigitalOcean return 25 records per page by default. Inspect at most 2 pages
assertTrue(api().list().concat().limit(50).allMatch(new Predicate<Action>() {
@Override
public boolean apply(Action input) {
found.incrementAndGet();
return !isNullOrEmpty(input.type());
}
}), "All actions must have the 'type' field populated");
assertTrue(found.get() > 0, "Expected some actions to be returned");
}
public void testListActionsOnePage() {
final AtomicInteger found = new AtomicInteger(0);
assertTrue(api().list(page(1).perPage(5)).allMatch(new Predicate<Action>() {
@Override
public boolean apply(Action input) {
found.incrementAndGet();
return !isNullOrEmpty(input.type());
}
}), "All actions must have the 'type' field populated");
assertTrue(found.get() > 0, "Expected some actions to be returned");
}
public void testGetAction() {
Optional<Action> first = api().list().concat().first();
assertTrue(first.isPresent(), "At least one action was expected to exist");
assertNotNull(api().get(first.get().id()));
}
private ActionApi api() {
return api.actionApi();
}
}

View File

@ -0,0 +1,110 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.digitalocean2.features;
import static com.google.common.collect.Iterables.isEmpty;
import static com.google.common.collect.Iterables.size;
import static org.jclouds.digitalocean2.domain.options.ListOptions.Builder.page;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
import java.util.Map;
import org.jclouds.digitalocean2.domain.Action;
import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiMockTest;
import org.testng.annotations.Test;
import com.google.common.reflect.TypeToken;
@Test(groups = "unit", testName = "ActionApiMockTest", singleThreaded = true)
public class ActionApiMockTest extends BaseDigitalOcean2ApiMockTest {
public void testListActions() throws InterruptedException {
server.enqueue(jsonResponse("/actions-first.json"));
server.enqueue(jsonResponse("/actions-last.json"));
Iterable<Action> actions = api.actionApi().list().concat();
assertEquals(size(actions), 8); // Force the PagedIterable to advance
assertEquals(server.getRequestCount(), 2);
assertSent(server, "GET", "/actions");
assertSent(server, "GET", "/actions?page=2&per_page=5");
}
public void testListActionsReturns404() throws InterruptedException {
server.enqueue(response404());
Iterable<Action> actions = api.actionApi().list().concat();
assertTrue(isEmpty(actions));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/actions");
}
public void testListActionsWithOptions() throws InterruptedException {
server.enqueue(jsonResponse("/actions-first.json"));
Iterable<Action> actions = api.actionApi().list(page(1).perPage(5));
assertEquals(size(actions), 5);
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/actions?page=1&per_page=5");
}
public void testListActionsWithOptionsReturns404() throws InterruptedException {
server.enqueue(response404());
Iterable<Action> actions = api.actionApi().list(page(1).perPage(5));
assertTrue(isEmpty(actions));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/actions?page=1&per_page=5");
}
public void testGetAction() throws InterruptedException {
server.enqueue(jsonResponse("/action.json"));
Action action = api.actionApi().get(1);
assertEquals(action, actionFromResource("/action.json"));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/actions/1");
}
public void testGetActionReturns404() throws InterruptedException {
server.enqueue(response404());
Action action = api.actionApi().get(1);
assertNull(action);
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/actions/1");
}
private Action actionFromResource(String resource) {
return onlyObjectFromResource(resource, new TypeToken<Map<String, Action>>() {
private static final long serialVersionUID = 1L;
});
}
}

View File

@ -0,0 +1,186 @@
/*
* 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.digitalocean2.features;
import static java.util.logging.Logger.getAnonymousLogger;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
import java.util.List;
import java.util.Map;
import org.jclouds.compute.ComputeTestUtils;
import org.jclouds.digitalocean2.domain.Action;
import org.jclouds.digitalocean2.domain.Backup;
import org.jclouds.digitalocean2.domain.Droplet;
import org.jclouds.digitalocean2.domain.DropletCreate;
import org.jclouds.digitalocean2.domain.Image;
import org.jclouds.digitalocean2.domain.Kernel;
import org.jclouds.digitalocean2.domain.Key;
import org.jclouds.digitalocean2.domain.Region;
import org.jclouds.digitalocean2.domain.Size;
import org.jclouds.digitalocean2.domain.Snapshot;
import org.jclouds.digitalocean2.domain.options.CreateDropletOptions;
import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiLiveTest;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
@Test(groups = "live", testName = "DropletApiLiveTest")
public class DropletApiLiveTest extends BaseDigitalOcean2ApiLiveTest {
private Region region;
private Size size;
private Image image;
private Key key;
private int dropletId = -1;
@BeforeClass
public void setupDroplet() {
region = firstAvailableRegion();
size = cheapestSizeInRegion(region);
image = ubuntuImageInRegion(region);
Map<String, String> keyPair = ComputeTestUtils.setupKeyPair();
key = api.keyApi().create(prefix + "-droplet-livetest", keyPair.get("public"));
}
@AfterClass(alwaysRun = true)
public void tearDown() {
if (key != null) {
api.keyApi().delete(key.id());
}
}
public void testCreate() {
DropletCreate dropletCreate = api().create(prefix + "-droplet-livetest", region.slug(), size.slug(), image.slug(),
CreateDropletOptions.builder().backupsEnabled(true).addSshKeyId(key.id()).build());
dropletId = dropletCreate.droplet().id();
assertNodeRunning(dropletId);
Droplet droplet = api().get(dropletId);
assertNotNull(droplet, "Droplet should not be null");
}
@Test(groups = "live", dependsOnMethods = "testCreate")
public void testListDroplets() {
assertTrue(api().list().concat().anyMatch(new Predicate<Droplet>() {
@Override
public boolean apply(Droplet input) {
return input.id() == dropletId;
}
}), "The created droplet must be in the list");
}
@Test(dependsOnMethods = "testCreate")
public void testListKernels() {
Iterable<Kernel> kernels = api().listKernels(dropletId).concat();
assertEquals(kernels.iterator().next().name(), "DO-recovery-static-fsck");
}
@Test(dependsOnMethods = "testListKernels")
public void testPowerOff() {
api().powerOff(dropletId);
assertNodeStopped(dropletId);
}
@Test(groups = "live", dependsOnMethods = "testPowerOff")
public void testSnapshots() {
Action action = api().snapshot(dropletId, prefix + dropletId + "-snapshot");
assertActionCompleted(action.id());
List<Snapshot> snapshots = api().listSnapshots(dropletId).concat().toList();
assertEquals(snapshots.size(), 1, "Must contain 1 snapshot");
for (Snapshot snapshot : snapshots) {
try {
api.imageApi().delete(snapshot.id());
} catch (Exception ex) {
getAnonymousLogger().warning("Could not delete snapshot: " + snapshot.id());
}
}
}
@Test(groups = "live", dependsOnMethods = "testSnapshots")
public void testBackups() {
Iterable<Backup> backups = api().listBackups(dropletId).concat();
// Backups are automatically taken by DO on a weekly basis, so we can't guarantee
// there will be any backup available. Just check that the call succeeds
assertNotNull(backups);
}
@Test(groups = "live", dependsOnMethods = "testSnapshots")
public void testListActions() {
FluentIterable<Action> actions = api().listActions(dropletId).concat();
assertTrue(actions.anyMatch(new Predicate<Action>() {
@Override
public boolean apply(Action input) {
return "snapshot".equals(input.type());
}
}));
}
@Test(groups = "live", dependsOnMethods = "testSnapshots")
public void testPowerOn() {
// Apparently droplets are automatically powered on after the snapshot process
api().powerOff(dropletId);
assertNodeStopped(dropletId);
api().powerOn(dropletId);
assertNodeRunning(dropletId);
}
@Test(groups = "live", dependsOnMethods = "testPowerOn")
public void testReboot() {
Action action = api().reboot(dropletId);
assertActionCompleted(action.id());
assertNodeRunning(dropletId);
}
@Test(groups = "live", dependsOnMethods = "testReboot")
public void testPowerCycle() {
Action action = api().powerCycle(dropletId);
assertActionCompleted(action.id());
assertNodeRunning(dropletId);
}
@Test(groups = "live", dependsOnMethods = "testPowerCycle")
public void testShutdown() {
Action action = api().shutdown(dropletId);
assertActionCompleted(action.id());
// The shutdown action can fail if the shutdown command fails in the guest OS
// We can not guarantee that a graceful shutdown action will en up in the droplet
// being in OFF state
}
@Test(groups = "live", dependsOnMethods = "testShutdown", alwaysRun = true)
public void testDelete() throws InterruptedException {
if (dropletId != -1) {
api().delete(dropletId);
assertNodeTerminated(dropletId);
assertNull(api().get(dropletId));
}
}
private DropletApi api() {
return api.dropletApi();
}
}

View File

@ -0,0 +1,401 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.digitalocean2.features;
import static com.google.common.collect.Iterables.isEmpty;
import static com.google.common.collect.Iterables.size;
import static org.jclouds.digitalocean2.domain.options.ListOptions.Builder.page;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
import java.util.Map;
import org.jclouds.digitalocean2.domain.Action;
import org.jclouds.digitalocean2.domain.Backup;
import org.jclouds.digitalocean2.domain.Droplet;
import org.jclouds.digitalocean2.domain.DropletCreate;
import org.jclouds.digitalocean2.domain.Kernel;
import org.jclouds.digitalocean2.domain.Snapshot;
import org.jclouds.digitalocean2.domain.options.CreateDropletOptions;
import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiMockTest;
import org.testng.annotations.Test;
import com.google.common.reflect.TypeToken;
@Test(groups = "unit", testName = "DropletApiMockTest", singleThreaded = true)
public class DropletApiMockTest extends BaseDigitalOcean2ApiMockTest {
public void testListDroplets() throws InterruptedException {
server.enqueue(jsonResponse("/droplets-first.json"));
server.enqueue(jsonResponse("/droplets-last.json"));
Iterable<Droplet> droplets = api.dropletApi().list().concat();
assertEquals(size(droplets), 2); // Force the PagedIterable to advance
assertEquals(server.getRequestCount(), 2);
assertSent(server, "GET", "/droplets");
assertSent(server, "GET", "/droplets?page=2&per_page=1");
}
public void testListDropletsReturns404() throws InterruptedException {
server.enqueue(response404());
Iterable<Droplet> droplets = api.dropletApi().list().concat();
assertTrue(isEmpty(droplets));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/droplets");
}
public void testListDropletsWithOptions() throws InterruptedException {
server.enqueue(jsonResponse("/droplets-first.json"));
Iterable<Droplet> droplets = api.dropletApi().list(page(1).perPage(20));
assertEquals(size(droplets), 1);
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/droplets?page=1&per_page=20");
}
public void testListDropletsWithOptionsReturns404() throws InterruptedException {
server.enqueue(response404());
Iterable<Droplet> droplets = api.dropletApi().list(page(1).perPage(20));
assertTrue(isEmpty(droplets));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/droplets?page=1&per_page=20");
}
public void testGetDroplet() throws InterruptedException {
server.enqueue(jsonResponse("/droplet.json"));
Droplet droplet = api.dropletApi().get(1);
assertEquals(droplet, dropletFromResource("/droplet.json"));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/droplets/1");
}
public void testGetDropletReturns404() throws InterruptedException {
server.enqueue(response404());
Droplet droplet = api.dropletApi().get(1);
assertNull(droplet);
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/droplets/1");
}
public void testCreateDroplet() throws InterruptedException {
server.enqueue(jsonResponse("/droplet-create-res.json"));
DropletCreate droplet = api.dropletApi().create("digitalocean2-s-d5e", "sfo1", "512mb", "6374124", CreateDropletOptions.builder().addSshKeyId(421192).build());
assertEquals(droplet, objectFromResource("/droplet-create-res.json", DropletCreate.class));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "POST", "/droplets", stringFromResource("/droplet-create-req.json"));
}
public void testListKernels() throws InterruptedException {
server.enqueue(jsonResponse("/kernels-first.json"));
server.enqueue(jsonResponse("/kernels-last.json"));
Iterable<Kernel> kernels = api.dropletApi().listKernels(5425561).concat();
assertEquals(size(kernels), 10); // Force the PagedIterable to advance
assertEquals(server.getRequestCount(), 2);
assertSent(server, "GET", "/droplets/5425561/kernels");
assertSent(server, "GET", "/droplets/5425561/kernels?page=2");
}
public void testListKernelsReturns404() throws InterruptedException {
server.enqueue(response404());
Iterable<Kernel> kernels = api.dropletApi().listKernels(5425561).concat();
assertTrue(isEmpty(kernels));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/droplets/5425561/kernels");
}
public void testListKernelsWithOptions() throws InterruptedException {
server.enqueue(jsonResponse("/kernels-first.json"));
Iterable<Kernel> kernels = api.dropletApi().listKernels(5425561, page(1).perPage(20));
assertEquals(size(kernels), 5);
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/droplets/5425561/kernels?page=1&per_page=20");
}
public void testListKernelsWithOptionsReturns404() throws InterruptedException {
server.enqueue(response404());
Iterable<Kernel> kernels = api.dropletApi().listKernels(5425561, page(1).perPage(20));
assertTrue(isEmpty(kernels));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/droplets/5425561/kernels?page=1&per_page=20");
}
public void testListActions() throws InterruptedException {
server.enqueue(jsonResponse("/actions-first.json"));
server.enqueue(jsonResponse("/actions-last.json"));
Iterable<Action> actions = api.dropletApi().listActions(1).concat();
assertEquals(size(actions), 8); // Force the PagedIterable to advance
assertEquals(server.getRequestCount(), 2);
assertSent(server, "GET", "/droplets/1/actions");
assertSent(server, "GET", "/droplets/1/actions?page=2&per_page=5");
}
public void testListActionsReturns404() throws InterruptedException {
server.enqueue(response404());
Iterable<Action> actions = api.dropletApi().listActions(1).concat();
assertTrue(isEmpty(actions));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/droplets/1/actions");
}
public void testListActionsWithOptions() throws InterruptedException {
server.enqueue(jsonResponse("/actions-first.json"));
Iterable<Action> actions = api.dropletApi().listActions(1, page(1).perPage(5));
assertEquals(size(actions), 5);
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/droplets/1/actions?page=1&per_page=5");
}
public void testListActionsWithOptionsReturns404() throws InterruptedException {
server.enqueue(response404());
Iterable<Action> actions = api.dropletApi().listActions(1, page(1).perPage(5));
assertTrue(isEmpty(actions));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/droplets/1/actions?page=1&per_page=5");
}
public void testListBackups() throws InterruptedException {
server.enqueue(jsonResponse("/backups-first.json"));
server.enqueue(jsonResponse("/backups-last.json"));
Iterable<Backup> backups = api.dropletApi().listBackups(5425561).concat();
assertEquals(size(backups), 2); // Force the PagedIterable to advance
assertEquals(server.getRequestCount(), 2);
assertSent(server, "GET", "/droplets/5425561/backups");
assertSent(server, "GET", "/droplets/5425561/backups?page=2");
}
public void testListBackupsReturns404() throws InterruptedException {
server.enqueue(response404());
Iterable<Backup> backups = api.dropletApi().listBackups(5425561).concat();
assertTrue(isEmpty(backups));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/droplets/5425561/backups");
}
public void testListBackupsWithOptions() throws InterruptedException {
server.enqueue(jsonResponse("/backups-first.json"));
Iterable<Backup> backups = api.dropletApi().listBackups(5425561, page(1).perPage(20));
assertEquals(size(backups), 1);
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/droplets/5425561/backups?page=1&per_page=20");
}
public void testListBackupsWithOptionsReturns404() throws InterruptedException {
server.enqueue(response404());
Iterable<Backup> backups = api.dropletApi().listBackups(5425561, page(1).perPage(20));
assertTrue(isEmpty(backups));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/droplets/5425561/backups?page=1&per_page=20");
}
public void testListSnapshots() throws InterruptedException {
server.enqueue(jsonResponse("/snapshots-first.json"));
server.enqueue(jsonResponse("/snapshots-last.json"));
Iterable<Snapshot> snapshots = api.dropletApi().listSnapshots(5425561).concat();
assertEquals(size(snapshots), 2); // Force the PagedIterable to advance
assertEquals(server.getRequestCount(), 2);
assertSent(server, "GET", "/droplets/5425561/snapshots");
assertSent(server, "GET", "/droplets/5425561/snapshots?page=2");
}
public void testListSnapshotsReturns404() throws InterruptedException {
server.enqueue(response404());
Iterable<Snapshot> snapshots = api.dropletApi().listSnapshots(5425561).concat();
assertTrue(isEmpty(snapshots));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/droplets/5425561/snapshots");
}
public void testListSnapshotsWithOptions() throws InterruptedException {
server.enqueue(jsonResponse("/snapshots-first.json"));
Iterable<Snapshot> snapshots = api.dropletApi().listSnapshots(5425561, page(1).perPage(20));
assertEquals(size(snapshots), 1);
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/droplets/5425561/snapshots?page=1&per_page=20");
}
public void testListSnapshotsWithOptionsReturns404() throws InterruptedException {
server.enqueue(response404());
Iterable<Snapshot> snapshots = api.dropletApi().listSnapshots(5425561, page(1).perPage(20));
assertTrue(isEmpty(snapshots));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/droplets/5425561/snapshots?page=1&per_page=20");
}
public void testDeleteDroplet() throws InterruptedException {
server.enqueue(response204());
api.dropletApi().delete(1);
assertEquals(server.getRequestCount(), 1);
assertSent(server, "DELETE", "/droplets/1");
}
public void testDeleteDropletReturns404() throws InterruptedException {
server.enqueue(response404());
api.dropletApi().delete(1);
assertEquals(server.getRequestCount(), 1);
assertSent(server, "DELETE", "/droplets/1");
}
public void testPowerCycleDroplet() throws InterruptedException {
server.enqueue(jsonResponse("/power-cycle.json"));
Action action = api.dropletApi().powerCycle(1);
assertEquals(action, actionFromResource("/power-cycle.json"));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "POST", "/droplets/1/actions", "{\"type\":\"power_cycle\"}");
}
public void testPowerOn() throws InterruptedException {
server.enqueue(jsonResponse("/power-on.json"));
Action action = api.dropletApi().powerOn(1);
assertEquals(action, actionFromResource("/power-on.json"));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "POST", "/droplets/1/actions", "{\"type\":\"power_on\"}");
}
public void testPowerOff() throws InterruptedException {
server.enqueue(jsonResponse("/power-off.json"));
Action action = api.dropletApi().powerOff(1);
assertEquals(action, actionFromResource("/power-off.json"));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "POST", "/droplets/1/actions", "{\"type\":\"power_off\"}");
}
public void testReboot() throws InterruptedException {
server.enqueue(jsonResponse("/reboot.json"));
Action action = api.dropletApi().reboot(1);
assertEquals(action, actionFromResource("/reboot.json"));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "POST", "/droplets/1/actions", "{\"type\":\"reboot\"}");
}
public void testShutdown() throws InterruptedException {
server.enqueue(jsonResponse("/shutdown.json"));
Action action = api.dropletApi().shutdown(1);
assertEquals(action, actionFromResource("/shutdown.json"));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "POST", "/droplets/1/actions", "{\"type\":\"shutdown\"}");
}
public void testSnapshot() throws InterruptedException {
server.enqueue(jsonResponse("/snapshot.json"));
Action action = api.dropletApi().snapshot(1, "foo");
assertEquals(action, actionFromResource("/snapshot.json"));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "POST", "/droplets/1/actions", "{\"type\":\"snapshot\",\"name\":\"foo\"}");
}
private Droplet dropletFromResource(String resource) {
return onlyObjectFromResource(resource, new TypeToken<Map<String, Droplet>>() {
private static final long serialVersionUID = 1L;
});
}
private Action actionFromResource(String resource) {
return onlyObjectFromResource(resource, new TypeToken<Map<String, Action>>() {
private static final long serialVersionUID = 1L;
});
}
}

View File

@ -0,0 +1,97 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.digitalocean2.features;
import static org.jclouds.digitalocean2.domain.options.ImageListOptions.Builder.page;
import static org.jclouds.digitalocean2.domain.options.ImageListOptions.Builder.type;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import static org.testng.util.Strings.isNullOrEmpty;
import java.util.concurrent.atomic.AtomicInteger;
import org.jclouds.digitalocean2.domain.Image;
import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiLiveTest;
import org.testng.annotations.Test;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
@Test(groups = "live", testName = "ImageApiLiveTest")
public class ImageApiLiveTest extends BaseDigitalOcean2ApiLiveTest {
public void testListImages() {
final AtomicInteger found = new AtomicInteger(0);
// DigitalOcean return 25 records per page by default. Inspect at most 2 pages
assertTrue(api().list().concat().limit(50).allMatch(new Predicate<Image>() {
@Override
public boolean apply(Image input) {
found.incrementAndGet();
return !isNullOrEmpty(input.name());
}
}), "All images must have the 'name' field populated");
assertTrue(found.get() > 0, "Expected some images to be returned");
}
public void testListImagesOnePage() {
final AtomicInteger found = new AtomicInteger(0);
assertTrue(api().list(page(1).perPage(5)).allMatch(new Predicate<Image>() {
@Override
public boolean apply(Image input) {
found.incrementAndGet();
return !isNullOrEmpty(input.name());
}
}), "All images must have the 'name' field populated");
assertTrue(found.get() > 0, "Expected some images to be returned");
}
public void testListImagesByType() {
final AtomicInteger found = new AtomicInteger(0);
assertTrue(api().list(type("distribution").perPage(5)).allMatch(new Predicate<Image>() {
@Override
public boolean apply(Image input) {
found.incrementAndGet();
return !isNullOrEmpty(input.distribution());
}
}), "All images must have the 'distribution' field populated");
assertTrue(found.get() > 0, "Expected some images to be returned");
}
public void testGetImage() {
Optional<Image> first = api().list().concat().first();
assertTrue(first.isPresent(), "At least one image was expected to exist");
assertNotNull(api().get(first.get().id()));
}
public void testGetImageBySlug() {
Optional<Image> first = api().list().concat().firstMatch(new Predicate<Image>() {
@Override
public boolean apply(Image input) {
return !isNullOrEmpty(input.slug());
}
});
assertTrue(first.isPresent(), "At least one image with the 'slug' field set was expected to exist");
assertNotNull(api().get(first.get().slug()));
}
// TODO: Delete live test once the create/transfer operations are implemented
private ImageApi api() {
return api.imageApi();
}
}

View File

@ -0,0 +1,150 @@
/*
* 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.digitalocean2.features;
import static com.google.common.collect.Iterables.isEmpty;
import static com.google.common.collect.Iterables.size;
import static org.jclouds.digitalocean2.domain.options.ImageListOptions.Builder.page;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
import java.util.Map;
import org.jclouds.digitalocean2.domain.Image;
import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiMockTest;
import org.testng.annotations.Test;
import com.google.common.reflect.TypeToken;
@Test(groups = "unit", testName = "ImageApiMockTest", singleThreaded = true)
public class ImageApiMockTest extends BaseDigitalOcean2ApiMockTest {
public void testListImages() throws InterruptedException {
server.enqueue(jsonResponse("/images-first.json"));
server.enqueue(jsonResponse("/images-last.json"));
Iterable<Image> images = api.imageApi().list().concat();
assertEquals(size(images), 10); // Force the PagedIterable to advance
assertEquals(server.getRequestCount(), 2);
assertSent(server, "GET", "/images");
assertSent(server, "GET", "/images?page=2&per_page=5&type=distribution");
}
public void testListImagesReturns404() throws InterruptedException {
server.enqueue(response404());
Iterable<Image> images = api.imageApi().list().concat();
assertTrue(isEmpty(images));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/images");
}
public void testListImagesWithOptions() throws InterruptedException {
server.enqueue(jsonResponse("/images-first.json"));
Iterable<Image> images = api.imageApi().list(page(1).perPage(5).type("distribution"));
assertEquals(size(images), 5);
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/images?page=1&per_page=5&type=distribution");
}
public void testListImagesWithOptionsReturns404() throws InterruptedException {
server.enqueue(response404());
Iterable<Image> images = api.imageApi().list(page(1).perPage(5).type("distribution"));
assertTrue(isEmpty(images));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/images?page=1&per_page=5&type=distribution");
}
public void testGetImage() throws InterruptedException {
server.enqueue(jsonResponse("/image.json"));
Image image = api.imageApi().get(1);
assertEquals(image, imageFromResource("/image.json"));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/images/1");
}
public void testGetImageReturns404() throws InterruptedException {
server.enqueue(response404());
Image image = api.imageApi().get(1);
assertNull(image);
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/images/1");
}
public void testGetImageUsingSlug() throws InterruptedException {
server.enqueue(jsonResponse("/image.json"));
Image image = api.imageApi().get("foo");
assertEquals(image, imageFromResource("/image.json"));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/images/foo");
}
public void testGetImageUsingSlugReturns404() throws InterruptedException {
server.enqueue(response404());
Image image = api.imageApi().get("foo");
assertNull(image);
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/images/foo");
}
public void testDeleteImage() throws InterruptedException {
server.enqueue(response204());
api.imageApi().delete(1);
assertEquals(server.getRequestCount(), 1);
assertSent(server, "DELETE", "/images/1");
}
public void testDeleteImageReturns404() throws InterruptedException {
server.enqueue(response404());
api.imageApi().delete(1);
assertEquals(server.getRequestCount(), 1);
assertSent(server, "DELETE", "/images/1");
}
private Image imageFromResource(String resource) {
return onlyObjectFromResource(resource, new TypeToken<Map<String, Image>>() {
private static final long serialVersionUID = 1L;
});
}
}

View File

@ -0,0 +1,99 @@
/*
* 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.digitalocean2.features;
import static org.jclouds.digitalocean2.domain.options.ListOptions.Builder.page;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import java.io.IOException;
import org.jclouds.digitalocean2.domain.Key;
import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiLiveTest;
import org.testng.annotations.AfterClass;
import org.testng.annotations.Test;
import com.google.common.base.Charsets;
import com.google.common.base.Throwables;
import com.google.common.collect.FluentIterable;
import com.google.common.io.Resources;
@Test(groups = "live", testName = "KeyApiLiveTest")
public class KeyApiLiveTest extends BaseDigitalOcean2ApiLiveTest {
private Key dsa;
private Key ecdsa;
public void testCreateKey() {
dsa = api().create("jclouds-test-dsa", loadKey("/ssh-dsa.pub"));
ecdsa = api().create("jclouds-test-ecdsa", loadKey("/ssh-ecdsa.pub"));
assertEquals(dsa.name(), "jclouds-test-dsa");
assertEquals(ecdsa.name(), "jclouds-test-ecdsa");
}
@Test(dependsOnMethods = "testCreateKey")
public void testListKeys() {
FluentIterable<Key> keys = api().list().concat();
assertTrue(keys.size() >= 2, "At least the two created keys must exist");
}
@Test(dependsOnMethods = "testCreateKey")
public void testListKeysOnePAge() {
FluentIterable<Key> keys = api().list(page(1));
assertTrue(keys.size() >= 2, "At least the two created keys must exist");
}
@Test(dependsOnMethods = "testCreateKey")
public void testGetKey() {
assertEquals(api().get(dsa.id()).fingerprint(), dsa.fingerprint());
assertEquals(api().get(ecdsa.fingerprint()).id(), ecdsa.id());
}
@Test(dependsOnMethods = "testCreateKey")
public void testUpdateKey() {
api().update(dsa.id(), "jclouds-test-dsa-updated");
assertEquals(api().get(dsa.id()).name(), "jclouds-test-dsa-updated");
}
@AfterClass(alwaysRun = true)
public void testDeleteKey() {
if (dsa != null) {
api().delete(dsa.id());
FluentIterable<Key> keys = api().list().concat();
assertFalse(keys.contains(dsa), "dsa key must not be present in list");
}
if (ecdsa != null) {
api().delete(ecdsa.fingerprint());
FluentIterable<Key> keys = api().list().concat();
assertFalse(keys.contains(ecdsa), "dsa key must not be present in list");
}
}
private String loadKey(String resourceName) {
try {
return Resources.toString(getClass().getResource(resourceName), Charsets.UTF_8);
} catch (IOException e) {
throw Throwables.propagate(e);
}
}
private KeyApi api() {
return api.keyApi();
}
}

View File

@ -0,0 +1,203 @@
/*
* 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.digitalocean2.features;
import static com.google.common.collect.Iterables.isEmpty;
import static com.google.common.collect.Iterables.size;
import static org.jclouds.digitalocean2.domain.options.ListOptions.Builder.page;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
import java.util.Map;
import org.jclouds.digitalocean2.domain.Key;
import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiMockTest;
import org.testng.annotations.Test;
import com.google.common.reflect.TypeToken;
@Test(groups = "unit", testName = "KeyApiMockTest", singleThreaded = true)
public class KeyApiMockTest extends BaseDigitalOcean2ApiMockTest {
public void testListKeys() throws InterruptedException {
server.enqueue(jsonResponse("/keys-first.json"));
server.enqueue(jsonResponse("/keys-last.json"));
Iterable<Key> keys = api.keyApi().list().concat();
assertEquals(size(keys), 7); // Force the PagedIterable to advance
assertEquals(server.getRequestCount(), 2);
assertSent(server, "GET", "/account/keys");
assertSent(server, "GET", "/account/keys?page=2&per_page=5");
}
public void testListKeysReturns404() throws InterruptedException {
server.enqueue(response404());
Iterable<Key> keys = api.keyApi().list().concat();
assertTrue(isEmpty(keys));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/account/keys");
}
public void testListKeysWithOptions() throws InterruptedException {
server.enqueue(jsonResponse("/keys-first.json"));
Iterable<Key> keys = api.keyApi().list(page(1).perPage(5));
assertEquals(size(keys), 5);
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/account/keys?page=1&per_page=5");
}
public void testListKeysWithOptionsReturns404() throws InterruptedException {
server.enqueue(response404());
Iterable<Key> keys = api.keyApi().list(page(1).perPage(5));
assertTrue(isEmpty(keys));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/account/keys?page=1&per_page=5");
}
public void testCreateKey() throws InterruptedException {
server.enqueue(jsonResponse("/key.json").setStatus("HTTP/1.1 201 Created"));
String dsa = stringFromResource("/ssh-dsa.pub");
Key key = api.keyApi().create("foo", dsa);
assertEquals(key, keyFromResource("/key.json"));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "POST", "/account/keys", String.format("{\"name\":\"foo\", \"public_key\":\"%s\"}", dsa));
}
public void testGetKey() throws InterruptedException {
server.enqueue(jsonResponse("/key.json"));
Key key = api.keyApi().get(1);
assertEquals(key, keyFromResource("/key.json"));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/account/keys/1");
}
public void testGetKeyReturns404() throws InterruptedException {
server.enqueue(response404());
Key key = api.keyApi().get(1);
assertNull(key);
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/account/keys/1");
}
public void testGetKeyUsingFingerprint() throws InterruptedException {
server.enqueue(jsonResponse("/key.json"));
Key key = api.keyApi().get("1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90");
assertEquals(key, keyFromResource("/key.json"));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/account/keys/1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90");
}
public void testGetKeyUsingFingerprintReturns404() throws InterruptedException {
server.enqueue(response404());
Key key = api.keyApi().get("1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90");
assertNull(key);
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/account/keys/1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90");
}
public void testUpdateKey() throws InterruptedException {
server.enqueue(jsonResponse("/key.json"));
Key key = api.keyApi().update(1, "foo");
assertEquals(key, keyFromResource("/key.json"));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "PUT", "/account/keys/1", "{\"name\":\"foo\"}");
}
public void testUpdateKeyUsingFingerprint() throws InterruptedException {
server.enqueue(jsonResponse("/key.json"));
Key key = api.keyApi().update("1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90", "foo");
assertEquals(key, keyFromResource("/key.json"));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "PUT", "/account/keys/1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90", "{\"name\":\"foo\"}");
}
public void testDeleteKey() throws InterruptedException {
server.enqueue(response204());
api.keyApi().delete(1);
assertEquals(server.getRequestCount(), 1);
assertSent(server, "DELETE", "/account/keys/1");
}
public void testDeleteKeyReturns404() throws InterruptedException {
server.enqueue(response404());
api.keyApi().delete(1);
assertEquals(server.getRequestCount(), 1);
assertSent(server, "DELETE", "/account/keys/1");
}
public void testDeleteKeyUsingFingerprint() throws InterruptedException {
server.enqueue(response204());
api.keyApi().delete("1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90");
assertEquals(server.getRequestCount(), 1);
assertSent(server, "DELETE", "/account/keys/1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90");
}
public void testDeleteKeyUsingfingerprintReturns404() throws InterruptedException {
server.enqueue(response404());
api.keyApi().delete("1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90");
assertEquals(server.getRequestCount(), 1);
assertSent(server, "DELETE", "/account/keys/1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90");
}
private Key keyFromResource(String resource) {
return onlyObjectFromResource(resource, new TypeToken<Map<String, Key>>() {
private static final long serialVersionUID = 1L;
});
}
}

View File

@ -0,0 +1,62 @@
/*
* 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.digitalocean2.features;
import static org.testng.Assert.assertTrue;
import static org.jclouds.digitalocean2.domain.options.ListOptions.Builder.page;
import java.util.concurrent.atomic.AtomicInteger;
import org.jclouds.digitalocean2.domain.Region;
import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiLiveTest;
import org.testng.annotations.Test;
import org.testng.util.Strings;
import com.google.common.base.Predicate;
@Test(groups = "live", testName = "RegionApiLiveTest")
public class RegionApiLiveTest extends BaseDigitalOcean2ApiLiveTest {
public void testListRegions() {
final AtomicInteger found = new AtomicInteger(0);
// DigitalOcean return 25 records per page by default. Inspect at most 2 pages
assertTrue(api().list().concat().limit(50).allMatch(new Predicate<Region>() {
@Override
public boolean apply(Region input) {
found.incrementAndGet();
return !Strings.isNullOrEmpty(input.slug());
}
}), "All regions must have the 'slug' field populated");
assertTrue(found.get() > 0, "Expected some regions to be returned");
}
public void testListRegionsOnePage() {
final AtomicInteger found = new AtomicInteger(0);
assertTrue(api().list(page(1)).allMatch(new Predicate<Region>() {
@Override
public boolean apply(Region input) {
found.incrementAndGet();
return !Strings.isNullOrEmpty(input.slug());
}
}), "All regions must have the 'slug' field populated");
assertTrue(found.get() > 0, "Expected some regions to be returned");
}
private RegionApi api() {
return api.regionApi();
}
}

View File

@ -0,0 +1,77 @@
/*
* 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.digitalocean2.features;
import static com.google.common.collect.Iterables.isEmpty;
import static com.google.common.collect.Iterables.size;
import static org.jclouds.digitalocean2.domain.options.ListOptions.Builder.page;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import org.jclouds.digitalocean2.domain.Region;
import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiMockTest;
import org.testng.annotations.Test;
@Test(groups = "unit", testName = "RegionApiMockTest", singleThreaded = true)
public class RegionApiMockTest extends BaseDigitalOcean2ApiMockTest {
public void testListRegions() throws InterruptedException {
server.enqueue(jsonResponse("/regions-first.json"));
server.enqueue(jsonResponse("/regions-last.json"));
Iterable<Region> regions = api.regionApi().list().concat();
assertEquals(size(regions), 10); // Force the PagedIterable to advance
assertEquals(server.getRequestCount(), 2);
assertSent(server, "GET", "/regions");
assertSent(server, "GET", "/regions?page=2&per_page=5");
}
public void testListRegionsReturns404() throws InterruptedException {
server.enqueue(response404());
Iterable<Region> regions = api.regionApi().list().concat();
assertTrue(isEmpty(regions));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/regions");
}
public void testListRegionsWithOptions() throws InterruptedException {
server.enqueue(jsonResponse("/regions-first.json"));
Iterable<Region> regions = api.regionApi().list(page(1).perPage(5));
assertEquals(size(regions), 5);
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/regions?page=1&per_page=5");
}
public void testListRegionsWithOptionsReturns404() throws InterruptedException {
server.enqueue(response404());
Iterable<Region> regions = api.regionApi().list(page(1).perPage(5));
assertTrue(isEmpty(regions));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/regions?page=1&per_page=5");
}
}

View File

@ -0,0 +1,62 @@
/*
* 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.digitalocean2.features;
import static org.jclouds.digitalocean2.domain.options.ListOptions.Builder.page;
import static org.testng.Assert.assertTrue;
import java.util.concurrent.atomic.AtomicInteger;
import org.jclouds.digitalocean2.domain.Size;
import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiLiveTest;
import org.testng.annotations.Test;
import org.testng.util.Strings;
import com.google.common.base.Predicate;
@Test(groups = "live", testName = "SizeApiLiveTest")
public class SizeApiLiveTest extends BaseDigitalOcean2ApiLiveTest {
public void testListSizes() {
final AtomicInteger found = new AtomicInteger(0);
// DigitalOcean return 25 records per page by default. Inspect at most 2 pages
assertTrue(api().list().concat().limit(50).allMatch(new Predicate<Size>() {
@Override
public boolean apply(Size input) {
found.incrementAndGet();
return !Strings.isNullOrEmpty(input.slug());
}
}), "All sizes must have the 'slug' field populated");
assertTrue(found.get() > 0, "Expected some sizes to be returned");
}
public void testListSizesOnePage() {
final AtomicInteger found = new AtomicInteger(0);
assertTrue(api().list(page(1)).allMatch(new Predicate<Size>() {
@Override
public boolean apply(Size input) {
found.incrementAndGet();
return !Strings.isNullOrEmpty(input.slug());
}
}), "All sizes must have the 'slug' field populated");
assertTrue(found.get() > 0, "Expected some sizes to be returned");
}
private SizeApi api() {
return api.sizeApi();
}
}

View File

@ -0,0 +1,77 @@
/*
* 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.digitalocean2.features;
import static com.google.common.collect.Iterables.isEmpty;
import static com.google.common.collect.Iterables.size;
import static org.jclouds.digitalocean2.domain.options.ListOptions.Builder.page;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import org.jclouds.digitalocean2.domain.Size;
import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiMockTest;
import org.testng.annotations.Test;
@Test(groups = "unit", testName = "SizeApiMockTest", singleThreaded = true)
public class SizeApiMockTest extends BaseDigitalOcean2ApiMockTest {
public void testListSizes() throws InterruptedException {
server.enqueue(jsonResponse("/sizes-first.json"));
server.enqueue(jsonResponse("/sizes-last.json"));
Iterable<Size> sizes = api.sizeApi().list().concat();
assertEquals(size(sizes), 9); // Force the PagedIterable to advance
assertEquals(server.getRequestCount(), 2);
assertSent(server, "GET", "/sizes");
assertSent(server, "GET", "/sizes?page=2&per_page=5");
}
public void testListSizesReturns404() throws InterruptedException {
server.enqueue(response404());
Iterable<Size> sizes = api.sizeApi().list().concat();
assertTrue(isEmpty(sizes));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/sizes");
}
public void testListSizesWithOptions() throws InterruptedException {
server.enqueue(jsonResponse("/sizes-first.json"));
Iterable<Size> sizes = api.sizeApi().list(page(1).perPage(5));
assertEquals(size(sizes), 5);
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/sizes?page=1&per_page=5");
}
public void testListSizesWithOptionsReturns404() throws InterruptedException {
server.enqueue(response404());
Iterable<Size> sizes = api.sizeApi().list(page(1).perPage(5));
assertTrue(isEmpty(sizes));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/sizes?page=1&per_page=5");
}
}

View File

@ -0,0 +1,65 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.digitalocean2.functions;
import static com.google.common.collect.Iterables.getOnlyElement;
import static org.jclouds.digitalocean2.domain.options.ImageListOptions.PRIVATE_PARAM;
import static org.jclouds.digitalocean2.domain.options.ImageListOptions.TYPE_PARAM;
import static org.jclouds.digitalocean2.domain.options.ListOptions.PAGE_PARAM;
import static org.jclouds.digitalocean2.domain.options.ListOptions.PER_PAGE_PARAM;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
import java.net.URI;
import org.jclouds.digitalocean2.domain.options.ImageListOptions;
import org.testng.annotations.Test;
import com.google.common.collect.Multimap;
@Test(groups = "unit", testName = "LinkToImageListOptionsTest")
public class LinkToImageListOptionsTest {
public void testNoOptions() {
LinkToImageListOptions function = new LinkToImageListOptions();
ImageListOptions options = function.apply(URI.create("https://api.digitalocean.com/v2/images"));
assertNotNull(options);
Multimap<String, String> params = options.buildQueryParameters();
assertFalse(params.containsKey(PAGE_PARAM));
assertFalse(params.containsKey(PER_PAGE_PARAM));
assertFalse(params.containsKey(TYPE_PARAM));
assertFalse(params.containsKey(PRIVATE_PARAM));
}
public void testWithOptions() {
LinkToImageListOptions function = new LinkToImageListOptions();
ImageListOptions options = function.apply(URI
.create("https://api.digitalocean.com/v2/images?page=1&per_page=5&type=distribution&private=true"));
assertNotNull(options);
Multimap<String, String> params = options.buildQueryParameters();
assertEquals(getOnlyElement(params.get(PAGE_PARAM)), "1");
assertEquals(getOnlyElement(params.get(PER_PAGE_PARAM)), "5");
assertEquals(getOnlyElement(params.get(TYPE_PARAM)), "distribution");
assertEquals(getOnlyElement(params.get(PRIVATE_PARAM)), "true");
}
}

View File

@ -0,0 +1,58 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.digitalocean2.functions;
import static com.google.common.collect.Iterables.getOnlyElement;
import static org.jclouds.digitalocean2.domain.options.ListOptions.PAGE_PARAM;
import static org.jclouds.digitalocean2.domain.options.ListOptions.PER_PAGE_PARAM;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
import java.net.URI;
import org.jclouds.digitalocean2.domain.options.ListOptions;
import org.testng.annotations.Test;
import com.google.common.collect.Multimap;
@Test(groups = "unit", testName = "LinkToListOptionsTest")
public class LinkToListOptionsTest {
public void testNoOptions() {
LinkToListOptions function = new LinkToListOptions();
ListOptions options = function.apply(URI.create("https://api.digitalocean.com/v2/actions"));
assertNotNull(options);
Multimap<String, String> params = options.buildQueryParameters();
assertFalse(params.containsKey(PAGE_PARAM));
assertFalse(params.containsKey(PER_PAGE_PARAM));
}
public void testWithOptions() {
LinkToListOptions function = new LinkToListOptions();
ListOptions options = function.apply(URI.create("https://api.digitalocean.com/v2/actions?page=2&per_page=5"));
assertNotNull(options);
Multimap<String, String> params = options.buildQueryParameters();
assertEquals(getOnlyElement(params.get(PAGE_PARAM)), "2");
assertEquals(getOnlyElement(params.get(PER_PAGE_PARAM)), "5");
}
}

View File

@ -0,0 +1,153 @@
/*
* 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.digitalocean2.handlers;
import static org.jclouds.digitalocean2.handlers.RateLimitRetryHandler.RATE_LIMIT_RESET_HEADER;
import static org.jclouds.http.HttpUtils.releasePayload;
import static org.jclouds.io.Payloads.newInputStreamPayload;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import java.io.ByteArrayInputStream;
import java.util.concurrent.TimeUnit;
import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse;
import org.jclouds.io.Payload;
import org.testng.annotations.Test;
import com.google.common.util.concurrent.Uninterruptibles;
@Test(groups = "unit", testName = "RateLimitRetryHandlerTest")
public class RateLimitRetryHandlerTest {
// Configure a safe timeout of one minute to abort the tests in case they get
// stuck
private static final long TEST_SAFE_TIMEOUT = 60000;
private final RateLimitRetryHandler rateLimitRetryHandler = new RateLimitRetryHandler();
@Test(timeOut = TEST_SAFE_TIMEOUT)
public void testDoNotRetryIfNoRateLimit() {
HttpCommand command = new HttpCommand(HttpRequest.builder().method("GET").endpoint("http://localhost").build());
HttpResponse response = HttpResponse.builder().statusCode(450).build();
assertFalse(rateLimitRetryHandler.shouldRetryRequest(command, response));
}
@Test(timeOut = TEST_SAFE_TIMEOUT)
public void testDoNotRetryIfNotReplayable() {
// InputStream payloads are not replayable
Payload payload = newInputStreamPayload(new ByteArrayInputStream(new byte[0]));
HttpCommand command = new HttpCommand(HttpRequest.builder().method("GET").endpoint("http://localhost")
.payload(payload).build());
HttpResponse response = HttpResponse.builder().statusCode(429).build();
try {
assertFalse(rateLimitRetryHandler.shouldRetryRequest(command, response));
} finally {
releasePayload(command.getCurrentRequest());
}
}
@Test(timeOut = TEST_SAFE_TIMEOUT)
public void testDoNotRetryIfNoRateLimitResetHeader() {
HttpCommand command = new HttpCommand(HttpRequest.builder().method("GET").endpoint("http://localhost").build());
HttpResponse response = HttpResponse.builder().statusCode(429).build();
assertFalse(rateLimitRetryHandler.shouldRetryRequest(command, response));
}
@Test(timeOut = TEST_SAFE_TIMEOUT)
public void testDoNotRetryIfTooMuchWait() {
// 5 minutes Unix epoch timestamp
long rateLimitResetEpoch = (System.currentTimeMillis() + 300000) / 1000;
HttpCommand command = new HttpCommand(HttpRequest.builder().method("GET").endpoint("http://localhost").build());
HttpResponse response = HttpResponse.builder().statusCode(429)
.addHeader(RATE_LIMIT_RESET_HEADER, String.valueOf(rateLimitResetEpoch)).build();
assertFalse(rateLimitRetryHandler.shouldRetryRequest(command, response));
}
@Test(timeOut = TEST_SAFE_TIMEOUT)
public void testRequestIsDelayed() {
// 5 seconds Unix epoch timestamp
long rateLimitResetEpoch = (System.currentTimeMillis() + 5000) / 1000;
HttpCommand command = new HttpCommand(HttpRequest.builder().method("GET").endpoint("http://localhost").build());
HttpResponse response = HttpResponse.builder().statusCode(429)
.addHeader(RATE_LIMIT_RESET_HEADER, String.valueOf(rateLimitResetEpoch)).build();
long start = System.currentTimeMillis();
assertTrue(rateLimitRetryHandler.shouldRetryRequest(command, response));
// Should have blocked the amount of time configured in the header. Use a
// smaller value to compensate the time it takes to reach the code that
// computes the amount of time to wait.
assertTrue(System.currentTimeMillis() - start > 2500);
}
@Test(timeOut = TEST_SAFE_TIMEOUT)
public void testDoNotRetryIfRequestIsAborted() throws Exception {
// 10 seconds Unix epoch timestamp
long rateLimitResetEpoch = (System.currentTimeMillis() + 10000) / 1000;
final HttpCommand command = new HttpCommand(HttpRequest.builder().method("GET").endpoint("http://localhost")
.build());
final HttpResponse response = HttpResponse.builder().statusCode(429)
.addHeader(RATE_LIMIT_RESET_HEADER, String.valueOf(rateLimitResetEpoch)).build();
final Thread requestThread = Thread.currentThread();
Thread killer = new Thread() {
@Override
public void run() {
Uninterruptibles.sleepUninterruptibly(2, TimeUnit.SECONDS);
requestThread.interrupt();
}
};
// Start the killer thread that will abort the rate limit wait
killer.start();
assertFalse(rateLimitRetryHandler.shouldRetryRequest(command, response));
}
@Test(timeOut = TEST_SAFE_TIMEOUT)
public void testIncrementsFailureCount() {
HttpCommand command = new HttpCommand(HttpRequest.builder().method("GET").endpoint("http://localhost").build());
HttpResponse response = HttpResponse.builder().statusCode(429).build();
rateLimitRetryHandler.shouldRetryRequest(command, response);
assertEquals(command.getFailureCount(), 1);
rateLimitRetryHandler.shouldRetryRequest(command, response);
assertEquals(command.getFailureCount(), 2);
rateLimitRetryHandler.shouldRetryRequest(command, response);
assertEquals(command.getFailureCount(), 3);
}
@Test(timeOut = TEST_SAFE_TIMEOUT)
public void testDisallowExcessiveRetries() {
HttpCommand command = new HttpCommand(HttpRequest.builder().method("GET").endpoint("http://localhost").build());
HttpResponse response = HttpResponse.builder().statusCode(429).addHeader(RATE_LIMIT_RESET_HEADER, "0").build();
for (int i = 0; i < 5; i++) {
assertTrue(rateLimitRetryHandler.shouldRetryRequest(command, response));
}
assertFalse(rateLimitRetryHandler.shouldRetryRequest(command, response));
}
}

View File

@ -0,0 +1,140 @@
/*
* 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.digitalocean2.internal;
import static com.google.common.base.Preconditions.checkState;
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.assertEquals;
import static org.testng.Assert.assertTrue;
import static org.testng.util.Strings.isNullOrEmpty;
import java.util.Properties;
import org.jclouds.apis.BaseApiLiveTest;
import org.jclouds.compute.config.ComputeServiceProperties;
import org.jclouds.digitalocean2.DigitalOcean2Api;
import org.jclouds.digitalocean2.config.DigitalOcean2RateLimitModule;
import org.jclouds.digitalocean2.domain.Action;
import org.jclouds.digitalocean2.domain.Image;
import org.jclouds.digitalocean2.domain.Region;
import org.jclouds.digitalocean2.domain.Size;
import com.google.common.base.Predicate;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Ordering;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Names;
public class BaseDigitalOcean2ApiLiveTest extends BaseApiLiveTest<DigitalOcean2Api> {
private Predicate<Integer> actionCompleted;
private Predicate<Integer> nodeTerminated;
private Predicate<Integer> nodeStopped;
private Predicate<Integer> nodeRunning;
public BaseDigitalOcean2ApiLiveTest() {
provider = "digitalocean2";
}
@Override protected Properties setupProperties() {
Properties props = super.setupProperties();
props.put(ComputeServiceProperties.POLL_INITIAL_PERIOD, 1000);
props.put(ComputeServiceProperties.POLL_MAX_PERIOD, 10000);
return props;
}
@Override protected DigitalOcean2Api create(Properties props, Iterable<Module> modules) {
Injector injector = newBuilder().modules(modules).overrides(props).buildInjector();
actionCompleted = injector.getInstance(Key.get(new TypeLiteral<Predicate<Integer>>(){}));
nodeTerminated = injector.getInstance(Key.get(new TypeLiteral<Predicate<Integer>>(){},
Names.named(TIMEOUT_NODE_TERMINATED)));
nodeStopped = injector.getInstance(Key.get(new TypeLiteral<Predicate<Integer>>(){},
Names.named(TIMEOUT_NODE_SUSPENDED)));
nodeRunning = injector.getInstance(Key.get(new TypeLiteral<Predicate<Integer>>(){},
Names.named(TIMEOUT_NODE_RUNNING)));
return injector.getInstance(DigitalOcean2Api.class);
}
@Override protected Iterable<Module> setupModules() {
return ImmutableSet.<Module> builder().addAll(super.setupModules()).add(new DigitalOcean2RateLimitModule())
.build();
}
protected void assertActionCompleted(int actionId) {
checkState(actionCompleted.apply(actionId), "Timeout waiting for action: %s", actionId);
Action action = api.actionApi().get(actionId);
assertEquals(action.status(), Action.Status.COMPLETED);
}
protected void assertNodeStopped(int dropletId) {
assertTrue(nodeStopped.apply(dropletId), String.format("Droplet %s did not stop in the configured timeout", dropletId));
}
protected void assertNodeRunning(int dropletId) {
assertTrue(nodeRunning.apply(dropletId), String.format("Droplet %s did not start in the configured timeout", dropletId));
}
protected void assertNodeTerminated(int dropletId) {
assertTrue(nodeTerminated.apply(dropletId), String.format("Droplet %s was not terminated in the configured timeout", dropletId));
}
protected Region firstAvailableRegion() {
return api.regionApi().list().concat().firstMatch(new Predicate<Region>() {
@Override
public boolean apply(Region input) {
return input.available();
}
}).get();
}
protected Size cheapestSizeInRegion(final Region region) {
return sizesByPrice().min(api.sizeApi().list().concat().filter(new Predicate<Size>() {
@Override
public boolean apply(Size input) {
return input.available() && input.regions().contains(region.slug());
}
}));
}
protected Image ubuntuImageInRegion(final Region region) {
return api.imageApi().list().concat().firstMatch(new Predicate<Image>() {
@Override
public boolean apply(Image input) {
return "Ubuntu".equalsIgnoreCase(input.distribution()) && !isNullOrEmpty(input.slug())
&& input.regions().contains(region.slug());
}
}).get();
}
protected static Ordering<Size> sizesByPrice() {
return new Ordering<Size>() {
@Override
public int compare(Size left, Size right) {
return ComparisonChain.start()
.compare(left.priceHourly(), right.priceHourly())
.compare(left.priceMonthly(), right.priceMonthly())
.result();
}
};
}
}

View File

@ -0,0 +1,142 @@
/*
* 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.digitalocean2.internal;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor;
import static org.testng.Assert.assertEquals;
import java.io.IOException;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.jclouds.ContextBuilder;
import org.jclouds.concurrent.config.ExecutorServiceModule;
import org.jclouds.digitalocean2.DigitalOcean2Api;
import org.jclouds.digitalocean2.DigitalOcean2ProviderMetadata;
import org.jclouds.json.Json;
import org.jclouds.rest.ApiContext;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import com.google.common.base.Charsets;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.Resources;
import com.google.common.reflect.TypeToken;
import com.google.gson.JsonParser;
import com.google.inject.Module;
import com.squareup.okhttp.mockwebserver.MockResponse;
import com.squareup.okhttp.mockwebserver.MockWebServer;
import com.squareup.okhttp.mockwebserver.RecordedRequest;
public class BaseDigitalOcean2ApiMockTest {
private static final String MOCK_BEARER_TOKEN = "c5401990f0c24135e8d6b5d260603fc71696d4738da9aa04a720229a01a2521d";
private static final String DEFAULT_ENDPOINT = new DigitalOcean2ProviderMetadata().getEndpoint();
private final Set<Module> modules = ImmutableSet.<Module> of(new ExecutorServiceModule(sameThreadExecutor()));
protected MockWebServer server;
protected DigitalOcean2Api api;
private Json json;
// So that we can ignore formatting.
private final JsonParser parser = new JsonParser();
@BeforeMethod
public void start() throws IOException {
server = new MockWebServer();
server.play();
ApiContext<DigitalOcean2Api> ctx = ContextBuilder.newBuilder("digitalocean2")
.credentials("", MOCK_BEARER_TOKEN)
.endpoint(url(""))
.modules(modules)
.overrides(overrides())
.build();
json = ctx.utils().injector().getInstance(Json.class);
api = ctx.getApi();
}
@AfterMethod(alwaysRun = true)
public void stop() throws IOException {
server.shutdown();
api.close();
}
protected Properties overrides() {
return new Properties();
}
protected String url(String path) {
return server.getUrl(path).toString();
}
protected MockResponse jsonResponse(String resource) {
return new MockResponse().addHeader("Content-Type", "application/json").setBody(stringFromResource(resource));
}
protected MockResponse response404() {
return new MockResponse().setStatus("HTTP/1.1 404 Not Found");
}
protected MockResponse response204() {
return new MockResponse().setStatus("HTTP/1.1 204 No Content");
}
protected String stringFromResource(String resourceName) {
try {
return Resources.toString(getClass().getResource(resourceName), Charsets.UTF_8)
.replace(DEFAULT_ENDPOINT, url(""));
} catch (IOException e) {
throw Throwables.propagate(e);
}
}
protected <T> T onlyObjectFromResource(String resourceName, TypeToken<Map<String, T>> type) {
// Assume JSON objects passed here will be in the form: { "entity": { ... } }
String text = stringFromResource(resourceName);
Map<String, T> object = json.fromJson(text, type.getType());
checkArgument(!object.isEmpty(), "The given json does not contain any object: %s", text);
checkArgument(object.keySet().size() == 1, "The given json does not contain more than one object: %s", text);
return object.get(getOnlyElement(object.keySet()));
}
protected <T> T objectFromResource(String resourceName, Class<T> type) {
String text = stringFromResource(resourceName);
return json.fromJson(text, type);
}
protected RecordedRequest assertSent(MockWebServer server, String method, String path) throws InterruptedException {
RecordedRequest request = server.takeRequest();
assertEquals(request.getMethod(), method);
assertEquals(request.getPath(), path);
assertEquals(request.getHeader("Accept"), "application/json");
assertEquals(request.getHeader("Authorization"), "Bearer " + MOCK_BEARER_TOKEN);
return request;
}
protected RecordedRequest assertSent(MockWebServer server, String method, String path, String json)
throws InterruptedException {
RecordedRequest request = assertSent(server, method, path);
assertEquals(request.getHeader("Content-Type"), "application/json");
assertEquals(parser.parse(new String(request.getBody(), Charsets.UTF_8)), parser.parse(json));
return request;
}
}

View File

@ -0,0 +1,54 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.digitalocean2.ssh;
import static org.testng.Assert.assertEquals;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.DSAPublicKey;
import java.security.spec.DSAPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import org.jclouds.util.Strings2;
import org.testng.annotations.Test;
/**
* Unit tests for the {@link DSAKeys} class.
*/
@Test(groups = "unit", testName = "DSAKeysTest")
public class DSAKeysTest {
private static final String expectedFingerPrint = "2a:54:bb:8e:ba:44:96:c8:6c:9c:40:34:3c:4d:38:e4";
@Test
public void testCanReadRsaAndCompareFingerprintOnPublicRSAKey() throws IOException {
String dsa = Strings2.toStringAndClose(getClass().getResourceAsStream("/ssh-dsa.pub"));
String fingerPrint = DSAKeys.fingerprintPublicKey(dsa);
assertEquals(fingerPrint, expectedFingerPrint);
}
@Test
public void testEncodeAsOpenSSH() throws IOException, InvalidKeySpecException, NoSuchAlgorithmException {
String dsa = Strings2.toStringAndClose(getClass().getResourceAsStream("/ssh-dsa.pub"));
DSAPublicKeySpec spec = DSAKeys.publicKeySpecFromOpenSSH(dsa);
DSAPublicKey key = (DSAPublicKey) KeyFactory.getInstance("DSA").generatePublic(spec);
assertEquals(DSAKeys.encodeAsOpenSSH(key), dsa);
}
}

View File

@ -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.digitalocean2.ssh;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import org.jclouds.util.Strings2;
import org.testng.annotations.Test;
/**
* Unit tests for the {@link ECDSAKeysTest} class.
*/
@Test(groups = "unit", testName = "ECDSAKeysTest")
public class ECDSAKeysTest {
private static final String expectedFingerPrint = "0e:9f:aa:cc:3e:79:5d:1e:f9:19:58:08:dc:c4:5e:1c";
@Test
public void testCanReadRsaAndCompareFingerprintOnPublicECDSAKey() throws IOException {
String ecdsa = Strings2.toStringAndClose(getClass().getResourceAsStream("/ssh-ecdsa.pub"));
String fingerPrint = ECDSAKeys.fingerprintPublicKey(ecdsa);
assertEquals(fingerPrint, expectedFingerPrint);
}
@Test
public void testEncodeAsOpenSSH() throws IOException, InvalidKeySpecException, NoSuchAlgorithmException {
String ecdsa = Strings2.toStringAndClose(getClass().getResourceAsStream("/ssh-ecdsa.pub"));
ECPublicKeySpec spec = ECDSAKeys.publicKeySpecFromOpenSSH(ecdsa);
ECPublicKey key = (ECPublicKey) KeyFactory.getInstance("EC").generatePublic(spec);
assertTrue(ecdsa.startsWith(ECDSAKeys.encodeAsOpenSSH(key)));
}
}

View File

@ -0,0 +1,33 @@
{
"action": {
"region" : {
"name" : "New York 1",
"available" : true,
"slug" : "nyc1",
"features" : [
"virtio",
"backups",
"metadata"
],
"sizes" : [
"512mb",
"8gb",
"16gb",
"32gb",
"48gb",
"64gb",
"1gb",
"2gb",
"4gb"
]
},
"started_at" : "2015-05-19T15:17:55Z",
"status" : "completed",
"resource_type" : "droplet",
"resource_id" : 5347489,
"region_slug" : "nyc1",
"id" : 50900149,
"completed_at" : "2015-05-19T15:18:01Z",
"type" : "destroy"
}
}

View File

@ -0,0 +1,168 @@
{
"actions" : [
{
"region" : {
"name" : "New York 1",
"available" : true,
"slug" : "nyc1",
"features" : [
"virtio",
"backups",
"metadata"
],
"sizes" : [
"512mb",
"8gb",
"16gb",
"32gb",
"48gb",
"64gb",
"1gb",
"2gb",
"4gb"
]
},
"started_at" : "2015-05-19T15:17:55Z",
"status" : "completed",
"resource_type" : "droplet",
"resource_id" : 5347489,
"region_slug" : "nyc1",
"id" : 50900149,
"completed_at" : "2015-05-19T15:18:01Z",
"type" : "destroy"
},
{
"started_at" : "2015-05-19T15:07:55Z",
"region" : {
"sizes" : [
"512mb",
"8gb",
"16gb",
"32gb",
"48gb",
"64gb",
"1gb",
"2gb",
"4gb"
],
"features" : [
"virtio",
"backups",
"metadata"
],
"available" : true,
"slug" : "nyc1",
"name" : "New York 1"
},
"status" : "completed",
"resource_type" : "droplet",
"region_slug" : "nyc1",
"id" : 50899364,
"resource_id" : 5346565,
"completed_at" : "2015-05-19T15:08:04Z",
"type" : "destroy"
},
{
"completed_at" : "2015-05-19T13:39:59Z",
"type" : "create",
"region" : {
"name" : "New York 1",
"sizes" : [
"512mb",
"8gb",
"16gb",
"32gb",
"48gb",
"64gb",
"1gb",
"2gb",
"4gb"
],
"features" : [
"virtio",
"backups",
"metadata"
],
"available" : true,
"slug" : "nyc1"
},
"started_at" : "2015-05-19T13:39:12Z",
"resource_type" : "droplet",
"status" : "completed",
"resource_id" : 5347489,
"region_slug" : "nyc1",
"id" : 50892713
},
{
"resource_id" : 5346565,
"region_slug" : "nyc1",
"id" : 50888077,
"status" : "completed",
"resource_type" : "droplet",
"region" : {
"name" : "New York 1",
"features" : [
"virtio",
"backups",
"metadata"
],
"available" : true,
"slug" : "nyc1",
"sizes" : [
"512mb",
"8gb",
"16gb",
"32gb",
"48gb",
"64gb",
"1gb",
"2gb",
"4gb"
]
},
"started_at" : "2015-05-19T12:37:23Z",
"type" : "create",
"completed_at" : "2015-05-19T12:38:13Z"
},
{
"completed_at" : "2015-05-19T11:33:00Z",
"type" : "destroy",
"status" : "completed",
"resource_type" : "droplet",
"started_at" : "2015-05-19T11:32:55Z",
"region" : {
"available" : true,
"features" : [
"virtio",
"backups",
"metadata"
],
"slug" : "nyc1",
"sizes" : [
"512mb",
"8gb",
"16gb",
"32gb",
"48gb",
"64gb",
"1gb",
"2gb",
"4gb"
],
"name" : "New York 1"
},
"region_slug" : "nyc1",
"id" : 50884032,
"resource_id" : 5344505
}
],
"links" : {
"pages" : {
"last" : "https://api.digitalocean.com/v2/actions?page=2&per_page=5",
"next" : "https://api.digitalocean.com/v2/actions?page=2&per_page=5"
}
},
"meta" : {
"total" : 8
}
}

View File

@ -0,0 +1,106 @@
{
"meta" : {
"total" : 8
},
"links" : {
"pages" : {
"first" : "https://api.digitalocean.com/v2/actions?page=1&per_page=5",
"prev" : "https://api.digitalocean.com/v2/actions?page=1&per_page=5"
}
},
"actions" : [
{
"region" : {
"sizes" : [
"512mb",
"8gb",
"16gb",
"32gb",
"48gb",
"64gb",
"1gb",
"2gb",
"4gb"
],
"available" : true,
"features" : [
"virtio",
"backups",
"metadata"
],
"slug" : "nyc1",
"name" : "New York 1"
},
"started_at" : "2014-01-18T22:39:08Z",
"type" : "create",
"resource_type" : "droplet",
"id" : 14115951,
"completed_at" : "2014-01-18T22:41:14Z",
"region_slug" : "nyc1",
"resource_id" : 1010699,
"status" : "completed"
},
{
"started_at" : "2014-01-18T22:39:06Z",
"type" : "create",
"resource_type" : "droplet",
"region" : {
"sizes" : [
"512mb",
"8gb",
"16gb",
"32gb",
"48gb",
"64gb",
"1gb",
"2gb",
"4gb"
],
"slug" : "nyc1",
"name" : "New York 1",
"features" : [
"virtio",
"backups",
"metadata"
],
"available" : true
},
"resource_id" : 1010697,
"region_slug" : "nyc1",
"status" : "completed",
"id" : 14115948,
"completed_at" : "2014-01-18T22:40:43Z"
},
{
"region_slug" : "nyc1",
"resource_id" : 1010698,
"status" : "completed",
"id" : 14115949,
"completed_at" : "2014-01-18T22:44:08Z",
"type" : "create",
"started_at" : "2014-01-18T22:39:06Z",
"resource_type" : "droplet",
"region" : {
"sizes" : [
"512mb",
"8gb",
"16gb",
"32gb",
"48gb",
"64gb",
"1gb",
"2gb",
"4gb"
],
"slug" : "nyc1",
"name" : "New York 1",
"available" : true,
"features" : [
"virtio",
"backups",
"metadata"
]
}
}
]
}

View File

@ -0,0 +1,26 @@
{
"backups": [
{
"id": 7622989,
"name": "example.com 2014-11-14",
"distribution": "Ubuntu",
"slug": null,
"public": false,
"regions": [
"nyc3"
],
"created_at": "2014-11-14T16:07:38Z",
"type": "snapshot",
"min_disk_size": 20
}
],
"links" : {
"pages" : {
"last" : "https://api.digitalocean.com/v2/droplets/3067509/backups?page=2",
"next" : "https://api.digitalocean.com/v2/droplets/3067509/backups?page=2"
}
},
"meta": {
"total": 2
}
}

View File

@ -0,0 +1,26 @@
{
"backups": [
{
"id": 76229890,
"name": "example.com 2014-11-14",
"distribution": "Ubuntu",
"slug": null,
"public": false,
"regions": [
"nyc3"
],
"created_at": "2014-11-14T16:07:38Z",
"type": "snapshot",
"min_disk_size": 20
}
],
"links" : {
"pages" : {
"first" : "https://api.digitalocean.com/v2/droplets/3067509/backups?page=1",
"prev" : "https://api.digitalocean.com/v2/droplets/3067509/backups?page=1"
}
},
"meta": {
"total": 2
}
}

View File

@ -0,0 +1,12 @@
{
"name": "digitalocean2-s-d5e",
"region": "sfo1",
"size": "512mb",
"image": "6374124",
"ssh_keys": [
421192
],
"backups": false,
"ipv6": false,
"private_networking": false
}

View File

@ -0,0 +1,35 @@
{
"droplet": {
"id": 2987224,
"name": "digitalocean2-s-d5e",
"memory": 512,
"vcpus": 1,
"disk": 20,
"locked": true,
"status": "new",
"kernel": {
"id": 70,
"name": "Ubuntu 10.04 x64 vmlinuz-2.6.32-41-server",
"version": "2.6.32-41-server"
},
"created_at": "2014-10-27T19:33:34Z",
"features": [
"virtio"
],
"backup_ids": [],
"snapshot_ids": [],
"image": {},
"size_slug": "512mb",
"networks": {},
"region": {}
},
"links": {
"actions": [
{
"id": 35383956,
"rel": "create",
"href": "https://api.digitalocean.com/v2/actions/35383956"
}
]
}
}

View File

@ -0,0 +1,105 @@
{
"droplet" :
{
"created_at" : "2015-05-25T15:50:48Z",
"region" : {
"name" : "New York 1",
"sizes" : [
"32gb",
"16gb",
"2gb",
"1gb",
"4gb",
"8gb",
"512mb",
"64gb",
"48gb"
],
"slug" : "nyc1",
"features" : [
"virtio",
"backups",
"metadata"
],
"available" : true
},
"id" : 5425561,
"disk" : 20,
"networks" : {
"v6" : [],
"v4" : [
{
"type" : "public",
"ip_address" : "162.243.167.46",
"netmask" : "255.255.255.0",
"gateway" : "162.243.167.1"
}
]
},
"backup_ids" : [],
"image" : {
"slug" : "ubuntu-14-10-x32",
"public" : true,
"created_at" : "2015-01-08T18:41:22Z",
"distribution" : "Ubuntu",
"id" : 9801951,
"type" : "snapshot",
"regions" : [
"nyc1",
"ams1",
"sfo1",
"nyc2",
"ams2",
"sgp1",
"lon1",
"nyc3",
"ams3",
"fra1"
],
"name" : "14.10 x32",
"min_disk_size" : 20
},
"vcpus" : 1,
"next_backup_window" : {
"end" : "2015-06-01T23:00:00Z",
"start" : "2015-06-01T00:00:00Z"
},
"locked" : false,
"snapshot_ids" : [],
"kernel" : {
"name" : "Ubuntu 14.10 x32 vmlinuz-3.16.0-28-generic",
"id" : 2926,
"version" : "3.16.0-28-generic"
},
"status" : "active",
"features" : [
"backups",
"virtio"
],
"size" : {
"price_hourly" : 0.00744,
"slug" : "512mb",
"disk" : 20,
"available" : true,
"transfer" : 1,
"price_monthly" : 5,
"regions" : [
"nyc1",
"sgp1",
"ams1",
"sfo1",
"nyc2",
"lon1",
"nyc3",
"ams3",
"ams2",
"fra1"
],
"memory" : 512,
"vcpus" : 1
},
"name" : "test1",
"size_slug" : "512mb",
"memory" : 512
}
}

View File

@ -0,0 +1,115 @@
{
"links" : {
"pages" : {
"next" : "https://api.digitalocean.com/v2/droplets/5425561?page=2&per_page=1",
"last" : "https://api.digitalocean.com/v2/droplets/5425561?page=2&per_page=1"
}
},
"meta" : {
"total" : 2
},
"droplets" : [
{
"created_at" : "2015-05-25T15:50:48Z",
"region" : {
"name" : "New York 1",
"sizes" : [
"32gb",
"16gb",
"2gb",
"1gb",
"4gb",
"8gb",
"512mb",
"64gb",
"48gb"
],
"slug" : "nyc1",
"features" : [
"virtio",
"backups",
"metadata"
],
"available" : true
},
"id" : 5425561,
"disk" : 20,
"networks" : {
"v6" : [],
"v4" : [
{
"type" : "public",
"ip_address" : "162.243.167.46",
"netmask" : "255.255.255.0",
"gateway" : "162.243.167.1"
}
]
},
"backup_ids" : [],
"image" : {
"slug" : "ubuntu-14-10-x32",
"public" : true,
"created_at" : "2015-01-08T18:41:22Z",
"distribution" : "Ubuntu",
"id" : 9801951,
"type" : "snapshot",
"regions" : [
"nyc1",
"ams1",
"sfo1",
"nyc2",
"ams2",
"sgp1",
"lon1",
"nyc3",
"ams3",
"fra1"
],
"name" : "14.10 x32",
"min_disk_size" : 20
},
"vcpus" : 1,
"next_backup_window" : {
"end" : "2015-06-01T23:00:00Z",
"start" : "2015-06-01T00:00:00Z"
},
"locked" : false,
"snapshot_ids" : [],
"kernel" : {
"name" : "Ubuntu 14.10 x32 vmlinuz-3.16.0-28-generic",
"id" : 2926,
"version" : "3.16.0-28-generic"
},
"status" : "active",
"features" : [
"backups",
"virtio"
],
"size" : {
"price_hourly" : 0.00744,
"slug" : "512mb",
"disk" : 20,
"available" : true,
"transfer" : 1,
"price_monthly" : 5,
"regions" : [
"nyc1",
"sgp1",
"ams1",
"sfo1",
"nyc2",
"lon1",
"nyc3",
"ams3",
"ams2",
"fra1"
],
"memory" : 512,
"vcpus" : 1
},
"name" : "test1",
"size_slug" : "512mb",
"memory" : 512
}
]
}

View File

@ -0,0 +1,115 @@
{
"links" : {
"pages" : {
"first" : "https://api.digitalocean.com/v2/droplets/5425561?page=2&per_page=1",
"prev" : "https://api.digitalocean.com/v2/droplets/5425561?page=2&per_page=1"
}
},
"meta" : {
"total" : 2
},
"droplets" : [
{
"created_at" : "2015-05-25T15:50:48Z",
"region" : {
"name" : "New York 1",
"sizes" : [
"32gb",
"16gb",
"2gb",
"1gb",
"4gb",
"8gb",
"512mb",
"64gb",
"48gb"
],
"slug" : "nyc1",
"features" : [
"virtio",
"backups",
"metadata"
],
"available" : true
},
"id" : 5425561,
"disk" : 20,
"networks" : {
"v6" : [],
"v4" : [
{
"type" : "public",
"ip_address" : "162.243.167.46",
"netmask" : "255.255.255.0",
"gateway" : "162.243.167.1"
}
]
},
"backup_ids" : [],
"image" : {
"slug" : "ubuntu-14-10-x32",
"public" : true,
"created_at" : "2015-01-08T18:41:22Z",
"distribution" : "Ubuntu",
"id" : 9801951,
"type" : "snapshot",
"regions" : [
"nyc1",
"ams1",
"sfo1",
"nyc2",
"ams2",
"sgp1",
"lon1",
"nyc3",
"ams3",
"fra1"
],
"name" : "14.10 x32",
"min_disk_size" : 20
},
"vcpus" : 1,
"next_backup_window" : {
"end" : "2015-06-01T23:00:00Z",
"start" : "2015-06-01T00:00:00Z"
},
"locked" : false,
"snapshot_ids" : [],
"kernel" : {
"name" : "Ubuntu 14.10 x32 vmlinuz-3.16.0-28-generic",
"id" : 2926,
"version" : "3.16.0-28-generic"
},
"status" : "active",
"features" : [
"backups",
"virtio"
],
"size" : {
"price_hourly" : 0.00744,
"slug" : "512mb",
"disk" : 20,
"available" : true,
"transfer" : 1,
"price_monthly" : 5,
"regions" : [
"nyc1",
"sgp1",
"ams1",
"sfo1",
"nyc2",
"lon1",
"nyc3",
"ams3",
"ams2",
"fra1"
],
"memory" : 512,
"vcpus" : 1
},
"name" : "test1",
"size_slug" : "512mb",
"memory" : 512
}
]
}

View File

@ -0,0 +1,24 @@
{
"image" : {
"type" : "snapshot",
"id" : 11732785,
"name" : "Maintenance Mode",
"min_disk_size" : 20,
"distribution" : "Debian",
"created_at" : "2015-05-05T21:21:25Z",
"regions" : [
"nyc1",
"ams1",
"sfo1",
"nyc2",
"ams2",
"sgp1",
"lon1",
"nyc3",
"ams3",
"fra1"
],
"slug" : null,
"public" : true
}
}

View File

@ -0,0 +1,108 @@
{
"meta" : {
"total" : 54
},
"links" : {
"pages" : {
"next" : "https://api.digitalocean.com/v2/images?page=2&per_page=5&type=distribution",
"last" : "https://api.digitalocean.com/v2/images?page=11&per_page=5&type=distribution"
}
},
"images" : [
{
"type" : "snapshot",
"id" : 11732785,
"name" : "Maintenance Mode",
"min_disk_size" : 20,
"distribution" : "Debian",
"created_at" : "2015-05-05T21:21:25Z",
"regions" : [
"nyc1",
"ams1",
"sfo1",
"nyc2",
"ams2",
"sgp1",
"lon1",
"nyc3",
"ams3",
"fra1"
],
"slug" : null,
"public" : true
},
{
"type" : "snapshot",
"id" : 11833262,
"distribution" : "CoreOS",
"created_at" : "2015-05-12T17:41:36Z",
"regions" : [
"nyc1",
"sfo1",
"ams2",
"sgp1",
"lon1",
"nyc3",
"ams3",
"fra1"
],
"public" : true,
"slug" : "coreos-stable",
"name" : "647.0.0 (stable)",
"min_disk_size" : 20
},
{
"min_disk_size" : 20,
"name" : "668.3.0 (beta)",
"created_at" : "2015-05-18T18:14:12Z",
"public" : true,
"regions" : [
"nyc1",
"sfo1",
"ams2",
"sgp1",
"lon1",
"nyc3",
"ams3",
"fra1"
],
"slug" : "coreos-beta",
"distribution" : "CoreOS",
"id" : 11919888,
"type" : "snapshot"
},
{
"type" : "snapshot",
"id" : 11919908,
"distribution" : "CoreOS",
"created_at" : "2015-05-18T18:20:08Z",
"public" : true,
"regions" : [
"nyc1",
"sfo1",
"ams2",
"sgp1",
"lon1",
"nyc3",
"ams3",
"fra1"
],
"slug" : "coreos-alpha",
"name" : "681.0.0 (alpha)",
"min_disk_size" : 20
},
{
"min_disk_size" : 30,
"name" : "vum-easter-move",
"slug" : null,
"regions" : [
"ams1"
],
"public" : true,
"created_at" : "2015-04-10T07:31:20Z",
"distribution" : "Debian",
"id" : 11385199,
"type" : "snapshot"
}
]
}

View File

@ -0,0 +1,123 @@
{
"meta" : {
"total" : 54
},
"links" : {
"pages" : {
"prev" : "https://api.digitalocean.com/v2/images?page=1&per_page=5&type=distribution",
"first" : "https://api.digitalocean.com/v2/images?page=1&per_page=5&type=distribution"
}
},
"images" : [
{
"distribution" : "Fedora",
"min_disk_size" : 20,
"id" : 6370882,
"regions" : [
"nyc1",
"ams1",
"sfo1",
"nyc2",
"ams2",
"sgp1",
"lon1",
"nyc3",
"ams3",
"fra1"
],
"name" : "20 x64",
"type" : "snapshot",
"slug" : "fedora-20-x64",
"public" : true,
"created_at" : "2014-09-26T15:29:01Z"
},
{
"name" : "20 x32",
"type" : "snapshot",
"distribution" : "Fedora",
"regions" : [
"nyc1",
"ams1",
"sfo1",
"nyc2",
"ams2",
"sgp1",
"lon1",
"nyc3",
"ams3",
"fra1"
],
"id" : 6370885,
"min_disk_size" : 20,
"created_at" : "2014-09-26T15:29:18Z",
"public" : true,
"slug" : "fedora-20-x32"
},
{
"created_at" : "2014-09-26T16:40:18Z",
"slug" : "centos-5-8-x64",
"public" : true,
"type" : "snapshot",
"name" : "5.10 x64",
"regions" : [
"nyc1",
"ams1",
"sfo1",
"nyc2",
"ams2",
"sgp1",
"lon1",
"nyc3",
"ams3",
"fra1"
],
"min_disk_size" : 20,
"id" : 6372321,
"distribution" : "CentOS"
},
{
"public" : true,
"slug" : "centos-5-8-x32",
"created_at" : "2014-09-26T16:45:29Z",
"id" : 6372425,
"min_disk_size" : 20,
"regions" : [
"nyc1",
"ams1",
"sfo1",
"nyc2",
"ams2",
"sgp1",
"lon1",
"nyc3",
"ams3",
"fra1"
],
"distribution" : "CentOS",
"type" : "snapshot",
"name" : "5.10 x32"
},
{
"created_at" : "2014-09-26T16:56:00Z",
"public" : true,
"slug" : "debian-6-0-x64",
"type" : "snapshot",
"name" : "6.0 x64",
"regions" : [
"nyc1",
"ams1",
"sfo1",
"nyc2",
"ams2",
"sgp1",
"lon1",
"nyc3",
"ams3",
"fra1"
],
"min_disk_size" : 20,
"id" : 6372581,
"distribution" : "Debian"
}
]
}

View File

@ -0,0 +1,38 @@
{
"links" : {
"pages" : {
"next" : "https://api.digitalocean.com/v2/droplets/5425561/kernels?page=2",
"last" : "https://api.digitalocean.com/v2/droplets/5425561/kernels?page=2"
}
},
"meta" : {
"total" : 10
},
"kernels" : [
{
"id" : 231,
"version" : "3.8.0-25-generic",
"name" : "DO-recovery-static-fsck"
},
{
"name" : "Ubuntu 10.04 x32 vmlinuz-2.6.32-41-generic-pae",
"version" : "2.6.32-41-generic-pae",
"id" : 61
},
{
"name" : "Ubuntu 10.04 x32 vmlinuz-2.6.32-56-generic-pae",
"id" : 946,
"version" : "2.6.32-56-generic-pae"
},
{
"name" : "Ubuntu 10.04 x32 vmlinuz-2.6.32-57-generic-pae",
"id" : 987,
"version" : "2.6.32-57-generic-pae"
},
{
"name" : "Ubuntu 10.04 x32 vmlinuz-2.6.32-58-generic-pae",
"id" : 1269,
"version" : "2.6.32-58-generic-pae"
}
]
}

Some files were not shown because too many files have changed in this diff Show More