mirror of https://github.com/apache/jclouds.git
Promote DigitalOcean v2
This commit is contained in:
commit
886aa156b4
|
@ -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>
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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() { }
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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() {}
|
||||
}
|
|
@ -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() {}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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() {}
|
||||
}
|
|
@ -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() {}
|
||||
}
|
|
@ -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() {}
|
||||
}
|
|
@ -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() {}
|
||||
}
|
|
@ -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() {}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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)) : "";
|
||||
}
|
||||
|
||||
}
|
|
@ -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() {}
|
||||
}
|
|
@ -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() {}
|
||||
}
|
|
@ -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() {}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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, "");
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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"));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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)));
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"name": "digitalocean2-s-d5e",
|
||||
"region": "sfo1",
|
||||
"size": "512mb",
|
||||
"image": "6374124",
|
||||
"ssh_keys": [
|
||||
421192
|
||||
],
|
||||
"backups": false,
|
||||
"ipv6": false,
|
||||
"private_networking": false
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
Loading…
Reference in New Issue