[JCLOUDS-1430] Aliyun ECS initial skeleton
Project: http://git-wip-us.apache.org/repos/asf/jclouds-labs/repo Commit: http://git-wip-us.apache.org/repos/asf/jclouds-labs/commit/5bd2a80f Tree: http://git-wip-us.apache.org/repos/asf/jclouds-labs/tree/5bd2a80f Diff: http://git-wip-us.apache.org/repos/asf/jclouds-labs/diff/5bd2a80f Branch: refs/heads/master Commit: 5bd2a80fa34ce78d14317a366b049e5bce10043b Parents: 1b5dd9f Author: andreaturli <[email protected]> Authored: Tue Jul 3 14:00:16 2018 +0200 Committer: andreaturli <[email protected]> Committed: Thu Jul 5 12:41:22 2018 +0200 ---------------------------------------------------------------------- aliyun-ecs/pom.xml | 147 ++++++++++ .../aliyun/ecs/ECSComputeServiceApi.java | 29 ++ .../ecs/ECSComputeServiceProviderMetadata.java | 77 +++++ .../aliyun/ecs/ECSServiceApiMetadata.java | 93 ++++++ .../config/ECSComputeServiceHttpApiModule.java | 38 +++ .../config/ECSComputeServiceParserModule.java | 60 ++++ .../aliyun/ecs/domain/DiskDeviceMapping.java | 37 +++ .../org/jclouds/aliyun/ecs/domain/Image.java | 94 ++++++ .../org/jclouds/aliyun/ecs/domain/Images.java | 33 +++ .../org/jclouds/aliyun/ecs/domain/Regions.java | 81 ++++++ .../java/org/jclouds/aliyun/ecs/domain/Tag.java | 35 +++ .../domain/internal/PaginatedCollection.java | 109 +++++++ .../ecs/domain/options/ListImagesOptions.java | 127 ++++++++ .../ecs/domain/options/PaginationOptions.java | 69 +++++ .../jclouds/aliyun/ecs/features/ImageApi.java | 104 +++++++ .../jclouds/aliyun/ecs/filters/FormSign.java | 134 +++++++++ .../ecs/functions/BaseToPagedIterable.java | 55 ++++ .../handlers/ECSComputeServiceErrorHandler.java | 66 +++++ .../compute/ECSComputeProviderMetadataTest.java | 30 ++ .../ecs/compute/features/ImageApiLiveTest.java | 50 ++++ .../ecs/compute/features/ImageApiMockTest.java | 79 +++++ .../BaseECSComputeServiceApiLiveTest.java | 49 ++++ .../BaseECSComputeServiceApiMockTest.java | 118 ++++++++ aliyun-ecs/src/test/resources/images-first.json | 291 +++++++++++++++++++ aliyun-ecs/src/test/resources/images-last.json | 235 +++++++++++++++ .../src/test/resources/images-second.json | 291 +++++++++++++++++++ pom.xml | 1 + 27 files changed, 2532 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5bd2a80f/aliyun-ecs/pom.xml ---------------------------------------------------------------------- diff --git a/aliyun-ecs/pom.xml b/aliyun-ecs/pom.xml new file mode 100644 index 0000000..da1b900 --- /dev/null +++ b/aliyun-ecs/pom.xml @@ -0,0 +1,147 @@ +<?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:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" + 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.2.0-SNAPSHOT</version> + </parent> + <artifactId>aliyun-ecs</artifactId> + <name>jclouds Alibaba Elastic Compute Service API</name> + <description>jclouds components to access an implementation of Alibaba Elastic Compute Service</description> + <packaging>bundle</packaging> + + <properties> + <test.aliyun-ecs.endpoint>https://ecs.aliyuncs.com/</test.aliyun-ecs.endpoint> + <test.aliyun-ecs.api-version></test.aliyun-ecs.api-version> + <test.aliyun-ecs.build-version/> + <test.aliyun-ecs.identity>FIXME_IDENTITY</test.aliyun-ecs.identity> + <test.aliyun-ecs.credential>FIXME_CREDENTIALS</test.aliyun-ecs.credential> + <test.aliyun-ecs.template/> + + <jclouds.osgi.export>org.jclouds.aliyun.ecs.*;version="${project.version}"</jclouds.osgi.export> + <jclouds.osgi.import>org.jclouds*;version="${project.version}",*</jclouds.osgi.import> + <jclouds.osgi.dynamic>*</jclouds.osgi.dynamic> + </properties> + + <dependencies> + <dependency> + <groupId>org.apache.jclouds</groupId> + <artifactId>jclouds-compute</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>org.apache.jclouds</groupId> + <artifactId>jclouds-core</artifactId> + <version>${project.version}</version> + <type>test-jar</type> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.jclouds</groupId> + <artifactId>jclouds-compute</artifactId> + <version>${project.version}</version> + <type>test-jar</type> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.jclouds.driver</groupId> + <artifactId>jclouds-sshj</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.jclouds.driver</groupId> + <artifactId>jclouds-slf4j</artifactId> + <version>${project.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> + <!-- provided by the jclouds-bouncycastle driver --> + <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> + <optional>true</optional> + </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.aliyun-ecs.endpoint>${test.aliyun-ecs.endpoint}</test.aliyun-ecs.endpoint> + <test.aliyun-ecs.api-version>${test.aliyun-ecs.api-version} + </test.aliyun-ecs.api-version> + <test.aliyun-ecs.build-version>${test.aliyun-ecs.build-version} + </test.aliyun-ecs.build-version> + <test.aliyun-ecs.identity>${test.aliyun-ecs.identity}</test.aliyun-ecs.identity> + <test.aliyun-ecs.credential>${test.aliyun-ecs.credential} + </test.aliyun-ecs.credential> + <test.aliyun-ecs.template>${test.aliyun-ecs.template}</test.aliyun-ecs.template> + </systemPropertyVariables> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + </profiles> + + +</project> + http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5bd2a80f/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSComputeServiceApi.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSComputeServiceApi.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSComputeServiceApi.java new file mode 100644 index 0000000..268d773 --- /dev/null +++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSComputeServiceApi.java @@ -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.aliyun.ecs; + +import org.jclouds.aliyun.ecs.features.ImageApi; +import org.jclouds.rest.annotations.Delegate; + +import java.io.Closeable; + +public interface ECSComputeServiceApi extends Closeable { + + @Delegate + ImageApi imageApi(); + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5bd2a80f/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSComputeServiceProviderMetadata.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSComputeServiceProviderMetadata.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSComputeServiceProviderMetadata.java new file mode 100644 index 0000000..61b4df9 --- /dev/null +++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSComputeServiceProviderMetadata.java @@ -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.aliyun.ecs; + +import com.google.auto.service.AutoService; +import org.jclouds.providers.ProviderMetadata; +import org.jclouds.providers.internal.BaseProviderMetadata; + +import java.net.URI; +import java.util.Properties; + +@AutoService(ProviderMetadata.class) +public class ECSComputeServiceProviderMetadata extends BaseProviderMetadata { + + public ECSComputeServiceProviderMetadata() { + super(builder()); + } + + public ECSComputeServiceProviderMetadata(Builder builder) { + super(builder); + } + + public static Builder builder() { + return new Builder(); + } + + public static Properties defaultProperties() { + final Properties properties = ECSServiceApiMetadata.defaultProperties(); + return properties; + } + + @Override + public Builder toBuilder() { + return builder().fromProviderMetadata(this); + } + + public static class Builder extends BaseProviderMetadata.Builder { + + protected Builder() { + id("aliyun-ecs") + .name("Alibaba Elastic Compute Service") + .apiMetadata(new ECSServiceApiMetadata()) + .homepage(URI.create("https://www.alibabacloud.com")) + .console(URI.create("https://ecs.console.aliyun.com")) + .endpoint("https://ecs.aliyuncs.com") + .iso3166Codes("US-CA", "US-VA", "DE", "JP", "ID-JK", "SG", "IN", "AU-NSW", "MY", "CN-HE", "CN-SH", "CN-ZJ", "CN-GD", "HK", "AE-DU") // TODO + .defaultProperties(ECSServiceApiMetadata.defaultProperties()); + } + + @Override + public ECSComputeServiceProviderMetadata build() { + return new ECSComputeServiceProviderMetadata(this); + } + + @Override + public Builder fromProviderMetadata(ProviderMetadata in) { + super.fromProviderMetadata(in); + return this; + } + } +} + + http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5bd2a80f/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSServiceApiMetadata.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSServiceApiMetadata.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSServiceApiMetadata.java new file mode 100644 index 0000000..d81ef96 --- /dev/null +++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSServiceApiMetadata.java @@ -0,0 +1,93 @@ +/* + * 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.aliyun.ecs; + +import com.google.common.collect.ImmutableSet; +import com.google.inject.Module; +import org.jclouds.aliyun.ecs.config.ECSComputeServiceHttpApiModule; +import org.jclouds.aliyun.ecs.config.ECSComputeServiceParserModule; +import org.jclouds.apis.ApiMetadata; +import org.jclouds.compute.ComputeServiceContext; +import org.jclouds.rest.internal.BaseHttpApiMetadata; + +import java.net.URI; +import java.util.Properties; + +import static org.jclouds.compute.config.ComputeServiceProperties.TEMPLATE; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED; +import static org.jclouds.reflect.Reflection2.typeToken; + +public class ECSServiceApiMetadata extends BaseHttpApiMetadata<ECSComputeServiceApi> { + + public static final String DEFAULT_API_VERSION = "2014-05-26"; + + public ECSServiceApiMetadata() { + this(new Builder()); + } + + protected ECSServiceApiMetadata(Builder builder) { + super(builder); + } + + public static Properties defaultProperties() { + Properties properties = BaseHttpApiMetadata.defaultProperties(); + properties.put(TEMPLATE, "osFamily=CENTOS,os64Bit=true,osVersionMatches=7.4"); + properties.put(TIMEOUT_NODE_RUNNING, 900000); // 15 mins + properties.put(TIMEOUT_NODE_SUSPENDED, 900000); // 15 mins + return properties; + } + + @Override + public Builder toBuilder() { + return new Builder().fromApiMetadata(this); + } + + public static class Builder extends BaseHttpApiMetadata.Builder<ECSComputeServiceApi, Builder> { + + protected Builder() { + id("aliyun-ecs") + .name("Alibaba Elastic Compute Service API") + .identityName("user name") + .credentialName("user password") + .version(DEFAULT_API_VERSION) + .documentation(URI.create("https://www.alibabacloud.com/help")) + .defaultEndpoint("https://ecs.aliyuncs.com") + .defaultProperties(ECSServiceApiMetadata.defaultProperties()) + .view(typeToken(ComputeServiceContext.class)) + .defaultModules(ImmutableSet.<Class<? extends Module>>builder() + .add(ECSComputeServiceHttpApiModule.class) + .add(ECSComputeServiceParserModule.class) + .build()); + } + + @Override + public ECSServiceApiMetadata build() { + return new ECSServiceApiMetadata(this); + } + + @Override + protected Builder self() { + return this; + } + + @Override + public Builder fromApiMetadata(ApiMetadata in) { + return this; + } + } +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5bd2a80f/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/config/ECSComputeServiceHttpApiModule.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/config/ECSComputeServiceHttpApiModule.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/config/ECSComputeServiceHttpApiModule.java new file mode 100644 index 0000000..05ebc9f --- /dev/null +++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/config/ECSComputeServiceHttpApiModule.java @@ -0,0 +1,38 @@ +/* + * 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.aliyun.ecs.config; + +import org.jclouds.aliyun.ecs.ECSComputeServiceApi; +import org.jclouds.aliyun.ecs.handlers.ECSComputeServiceErrorHandler; +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.rest.ConfiguresHttpApi; +import org.jclouds.rest.config.HttpApiModule; + +@ConfiguresHttpApi +public class ECSComputeServiceHttpApiModule extends HttpApiModule<ECSComputeServiceApi> { + + @Override + protected void bindErrorHandlers() { + bind(HttpErrorHandler.class).annotatedWith(Redirection.class).to(ECSComputeServiceErrorHandler.class); + bind(HttpErrorHandler.class).annotatedWith(ClientError.class).to(ECSComputeServiceErrorHandler.class); + bind(HttpErrorHandler.class).annotatedWith(ServerError.class).to(ECSComputeServiceErrorHandler.class); + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5bd2a80f/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/config/ECSComputeServiceParserModule.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/config/ECSComputeServiceParserModule.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/config/ECSComputeServiceParserModule.java new file mode 100644 index 0000000..3e63fd1 --- /dev/null +++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/config/ECSComputeServiceParserModule.java @@ -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.aliyun.ecs.config; + +import com.google.common.base.CharMatcher; +import com.google.gson.stream.JsonReader; +import com.google.inject.AbstractModule; +import org.jclouds.date.DateService; +import org.jclouds.json.config.GsonModule; + +import javax.inject.Inject; +import java.io.IOException; +import java.util.Date; + +public class ECSComputeServiceParserModule extends AbstractModule { + + @Override + protected void configure() { + bind(GsonModule.DateAdapter.class).to(AliyunDateAdapter.class); + } + + /** + * Data adapter for the date formats used by Aliyun. + * <p> + * Essentially this is a workaround for the Aliyun getUsage() API call returning a corrupted form of ISO-8601 + * dates, which doesn't have seconds 2018-06-20T13:39Z + */ + public static class AliyunDateAdapter extends GsonModule.Iso8601DateAdapter { + + @Inject + AliyunDateAdapter(DateService dateService) { + super(dateService); + } + + public Date read(JsonReader reader) throws IOException { + String date = reader.nextString(); + int count = CharMatcher.is(':').countIn(date); + if (count < 2) { + date = date.replaceAll("Z", ":00Z"); + } + return parseDate(date); + } + + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5bd2a80f/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/DiskDeviceMapping.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/DiskDeviceMapping.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/DiskDeviceMapping.java new file mode 100644 index 0000000..1e257e1 --- /dev/null +++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/DiskDeviceMapping.java @@ -0,0 +1,37 @@ +/* + * 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.aliyun.ecs.domain; + +import com.google.auto.value.AutoValue; +import org.jclouds.json.SerializedNames; + +@AutoValue +public abstract class DiskDeviceMapping { + + DiskDeviceMapping() { + } + + @SerializedNames({"Device", "Size"}) + public static DiskDeviceMapping create(String device, String size) { + return new AutoValue_DiskDeviceMapping(device, size); + } + + public abstract String device(); + + public abstract String size(); + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5bd2a80f/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Image.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Image.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Image.java new file mode 100644 index 0000000..a65bb8b --- /dev/null +++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Image.java @@ -0,0 +1,94 @@ +/* + * 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.aliyun.ecs.domain; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableMap; +import org.jclouds.json.SerializedNames; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +@AutoValue +public abstract class Image { + + Image() {} + + @SerializedNames({"ImageId", "Description", "ProductCode", "OSType", "Architecture", "OSName", "DiskDeviceMappings", + "ImageOwnerAlias", "Progress", "IsSupportCloudinit", "Usage", "CreationTime", "Tags", + "ImageVersion", "Status", "ImageName", "IsSupportIoOptimized", "IsSelfShared", "IsCopied", + "IsSubscribed", "Platform", "Size"}) + public static Image create(String imageId, String description, String productCode, String osType, + String architecture, String osName, Map<String, List<DiskDeviceMapping>> diskDeviceMappings, + String imageOwnerAlias, String progress, Boolean isSupportCloudinit, String usage, Date creationTime, + Map<String, List<Tag>> tags, String imageVersion, String status, String imageName, + Boolean isSupportIoOptimized, Boolean isSelfShared, Boolean isCopied, Boolean isSubscribed, String platform, + String size) { + return new AutoValue_Image(imageId, description, productCode, osType, architecture, osName, + diskDeviceMappings == null ? + ImmutableMap.<String, List<DiskDeviceMapping>>of() : + ImmutableMap.copyOf(diskDeviceMappings), imageOwnerAlias, progress, isSupportCloudinit, usage, + creationTime, tags == null ? ImmutableMap.<String, List<Tag>>of() : ImmutableMap.copyOf(tags), imageVersion, + status, imageName, isSupportIoOptimized, isSelfShared, isCopied, isSubscribed, platform, size); + } + + public abstract String imageId(); + + public abstract String description(); + + public abstract String productCode(); + + public abstract String osType(); + + public abstract String architecture(); + + public abstract String osName(); + + public abstract Map<String, List<DiskDeviceMapping>> diskDeviceMappings(); + + public abstract String imageOwnerAlias(); + + public abstract String progress(); + + public abstract Boolean isSupportCloudinit(); + + public abstract String usage(); + + public abstract Date creationTime(); + + public abstract Map<String, List<Tag>> tags(); + + public abstract String imageVersion(); + + public abstract String status(); + + public abstract String imageName(); + + public abstract Boolean isSupportIoOptimizeds(); + + public abstract Boolean isSelfShared(); + + public abstract Boolean isCopied(); + + public abstract Boolean isSubscribed(); + + public abstract String platform(); + + public abstract String size(); + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5bd2a80f/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Images.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Images.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Images.java new file mode 100644 index 0000000..b0e3af3 --- /dev/null +++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Images.java @@ -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.aliyun.ecs.domain; + +import org.jclouds.aliyun.ecs.domain.internal.PaginatedCollection; + +import java.beans.ConstructorProperties; +import java.util.Map; + +/** + * A collection of Image + */ +public class Images extends PaginatedCollection<Image> { + + @ConstructorProperties({ "Images", "PageNumber", "TotalCount", "PageSize", "RegionId", "RequestId" }) + public Images(Map<String, Iterable<Image>> content, Integer pageNumber, Integer totalCount, Integer pageSize, String regionId, String requestId) { + super(content, pageNumber, totalCount, pageSize, regionId, requestId); + } +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5bd2a80f/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Regions.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Regions.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Regions.java new file mode 100644 index 0000000..e0a89c1 --- /dev/null +++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Regions.java @@ -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.aliyun.ecs.domain; + +/** + * Enumeration of region names + */ +public enum Regions { + + US_EAST_1("us-east-1", "US (Virginia)"), + US_WEST_1("us-west-1", "US West (Silicon Valley)"), + EU_CENTRAL_1("eu-central-1", "Germany (Frankfurt)"), + AP_NORTHEAST_1("ap-northeast-1", "Japan (Tokyo)"), + AP_SOUTH_1("ap-south-1", "India (Mumbai)"), + AP_SOUTHEAST_1("ap-southeast-1", "Singapore"), + AP_SOUTHEAST_2("ap-southeast-2", "Australia (Sydney)"), + AP_SOUTHEAST_3("ap-southeast-3", "Malaysia (Kuala Lumpur)"), + AP_SOUTHEAST_5("ap-southeast-5", "Indonesia (Jakarta)"), + CN_NORTH_1("cn-qingdao", "China (Qingdao)"), + CN_NORTH_2("cn-beijing", "China (Beijing)"), + CN_NORTH_3("cn-zhangjiakou", "China (Zhangjiakou)"), + CN_NORTH_5("cn-huhehaote", "China (Huhehaote)"), + CN_EAST_1("cn-hangzhou", "China (Hangzou)"), + CN_EAST_2("cn-shanghai", "China (Shanghai)"), + CN_SOUTH_1("cn-shenzhen", "China (Shenzhen)"), + CN_SOUTH_2("cn-hongkong", "China (Hongkong)"), + ME_EAST_1("me-east-1", "UAE (Dubai)"); + + private final String name; + private final String description; + + Regions(String name, String description) { + this.name = name; + this.description = description; + } + + /** + * The name of this region, used in the regions.xml file to identify it. + */ + public String getName() { + return name; + } + + /** + * Descriptive readable name for this region. + */ + public String getDescription() { + return description; + } + + /** + * Returns a region enum corresponding to the given region name. + * + * @param regionName + * The name of the region. Ex.: eu-west-1 + * @return Region enum representing the given region name. + */ + public static Regions fromName(String regionName) { + for (Regions region : Regions.values()) { + if (region.getName().equals(regionName)) { + return region; + } + } + throw new IllegalArgumentException("Cannot create enum from " + regionName + " value!"); + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5bd2a80f/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Tag.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Tag.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Tag.java new file mode 100644 index 0000000..1b34226 --- /dev/null +++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/Tag.java @@ -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.aliyun.ecs.domain; + +import com.google.auto.value.AutoValue; +import org.jclouds.json.SerializedNames; + +@AutoValue +public abstract class Tag { + + Tag() {} + + @SerializedNames({ "TagKey", "TagValue" }) + public static Tag create(String tagKey, String tagValue) { + return new AutoValue_Tag(tagKey, tagValue); + } + + public abstract String tagKey(); + + public abstract String tagValue(); +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5bd2a80f/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/internal/PaginatedCollection.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/internal/PaginatedCollection.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/internal/PaginatedCollection.java new file mode 100644 index 0000000..6cffd72 --- /dev/null +++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/internal/PaginatedCollection.java @@ -0,0 +1,109 @@ +/* + * 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.aliyun.ecs.domain.internal; + +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableMap; +import org.jclouds.aliyun.ecs.domain.options.PaginationOptions; +import org.jclouds.collect.IterableWithMarker; +import org.jclouds.javax.annotation.Nullable; + +import java.util.Iterator; +import java.util.Map; + +/** + * Base class for a paginated collection in Aliyun ECS. + */ +public class PaginatedCollection<T> extends IterableWithMarker<T> { + + private final Map<String, Iterable<T>> resources; + + /** + * The page number requested by the user or the default page (1) if none supplied. + */ + private final int pageNumber; + + /** + * The total number of result records matching request criteria. + */ + private final int totalCount; + + /** + * The page size used; either specified in the request or the default for the API function being requested. + */ + private final int pageSize; + + private final String regionId; + + private final String requestId; + + protected PaginatedCollection(@Nullable Map<String, Iterable<T>> resources, int pageNumber, int totalCount, + int pageSize, String regionId, String requestId) { + this.resources = resources != null ? resources : ImmutableMap.<String, Iterable<T>>of(); + this.pageNumber = pageNumber; + this.totalCount = totalCount; + this.pageSize = pageSize; + this.regionId = regionId; + this.requestId = requestId; + } + + public Iterable<T> getResources() { + return resources.entrySet().iterator().next().getValue(); + } + + public int getPageNumber() { + return pageNumber; + } + + public int getTotalCount() { + return totalCount; + } + + public int getPageSize() { + return pageSize; + } + + public String getRegionId() { + return regionId; + } + + public String getRequestId() { + return requestId; + } + + @Override + public Iterator<T> iterator() { + return resources.entrySet().iterator().next().getValue().iterator(); + } + + @Override + public Optional<Object> nextMarker() { + if (totalCount < pageSize) { + return Optional.absent(); + } + + if ((float) pageNumber < ((float) totalCount / (float) pageSize)) { + return Optional.of(toPaginationOptions(pageNumber + 1)); + } + return Optional.absent(); + } + + private Object toPaginationOptions(Integer pageNumber) { + return PaginationOptions.Builder.pageNumber(pageNumber); + } +} + http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5bd2a80f/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListImagesOptions.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListImagesOptions.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListImagesOptions.java new file mode 100644 index 0000000..d30c350 --- /dev/null +++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/ListImagesOptions.java @@ -0,0 +1,127 @@ +/* + * 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.aliyun.ecs.domain.options; + +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.collect.Iterables; +import org.jclouds.http.options.BaseHttpRequestOptions; + +import java.util.Arrays; + +public class ListImagesOptions extends BaseHttpRequestOptions { + public static final String IMAGE_ID_PARAM = "ImageId"; + public static final String STATUS_PARAM = "Status"; + public static final String SNAPSHOT_ID_PARAM = "SnapshotId"; + public static final String IMAGE_NAME_PARAM = "ImageName"; + public static final String IMAGE_OWNER_ALIAS_PARAM = "ImageOwnerAlias"; + public static final String USAGE_PARAM = "Usage"; + + public ListImagesOptions imageIds(String... instanceIds) { + String instanceIdsAsString = Joiner.on(",") + .join(Iterables.transform(Arrays.asList(instanceIds), new Function<String, String>() { + @Override + public String apply(String s) { + return new StringBuilder(s.length() + 1).append('"').append(s).append('"').toString(); + } + })); + queryParameters.put(IMAGE_ID_PARAM, String.format("[%s]", instanceIdsAsString)); + return this; + } + + public ListImagesOptions status(String status) { + queryParameters.put(STATUS_PARAM, status); + return this; + } + + public ListImagesOptions snapshotId(String snapshotId) { + queryParameters.put(SNAPSHOT_ID_PARAM, snapshotId); + return this; + } + + public ListImagesOptions imageName(String imageName) { + queryParameters.put(IMAGE_NAME_PARAM, imageName); + return this; + } + + public ListImagesOptions imageOwnerAlias(String imageOwnerAlias) { + queryParameters.put(IMAGE_OWNER_ALIAS_PARAM, imageOwnerAlias); + return this; + } + + public ListImagesOptions usage(String usage) { + queryParameters.put(USAGE_PARAM, usage); + return this; + } + + public ListImagesOptions paginationOptions(final PaginationOptions paginationOptions) { + this.queryParameters.putAll(paginationOptions.buildQueryParameters()); + return this; + } + + public static final class Builder { + + /** + * @see {@link ListImagesOptions#imageIds(String...)} + */ + public static ListImagesOptions imageIds(String... imageIds) { + return new ListImagesOptions().imageIds(imageIds); + } + + /** + * @see {@link ListImagesOptions#status(String)} + */ + public static ListImagesOptions status(String status) { + return new ListImagesOptions().status(status); + } + + /** + * @see {@link ListImagesOptions#snapshotId(String)} + */ + public static ListImagesOptions snapshotId(String snapshotId) { + return new ListImagesOptions().snapshotId(snapshotId); + } + + /** + * @see {@link ListImagesOptions#imageName(String)} + */ + public static ListImagesOptions imageName(String imageName) { + return new ListImagesOptions().imageName(imageName); + } + + /** + * @see {@link ListImagesOptions#imageOwnerAlias(String)} + */ + public static ListImagesOptions imageOwnerAlias(String imageOwnerAlias) { + return new ListImagesOptions().imageOwnerAlias(imageOwnerAlias); + } + + /** + * @see {@link ListImagesOptions#usage(String)} + */ + public static ListImagesOptions usage(String usage) { + return new ListImagesOptions().usage(usage); + } + + /** + * @see ListImagesOptions#paginationOptions(PaginationOptions) + */ + public static ListImagesOptions paginationOptions(PaginationOptions paginationOptions) { + return new ListImagesOptions().paginationOptions(paginationOptions); + } + } +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5bd2a80f/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/PaginationOptions.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/PaginationOptions.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/PaginationOptions.java new file mode 100644 index 0000000..2a7a0ab --- /dev/null +++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/domain/options/PaginationOptions.java @@ -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.aliyun.ecs.domain.options; + +import org.jclouds.http.options.BaseHttpRequestOptions; + +import static com.google.common.base.Preconditions.checkState; + +public class PaginationOptions extends BaseHttpRequestOptions { + + public static final String PAGE_NUMBER = "pageNumber"; + public static final String PAGE_SIZE = "pageSize"; + + public PaginationOptions pageNumber(int pageNumber) { + checkState(pageNumber > 0, "pageSize must be > 0"); + checkState(pageNumber <= 50, "limit must be <= 50"); + this.queryParameters.put(PAGE_NUMBER, Integer.toString(pageNumber)); + return this; + } + + public String pageNumber() { + return getFirstQueryOrNull(PAGE_NUMBER); + } + + public PaginationOptions pageSize(int pageSize) { + checkState(pageSize >= 0, "pageSize must be >= 0"); + checkState(pageSize <= 100, "pageSize must be <= 100"); + queryParameters.put(PAGE_SIZE, Integer.toString(pageSize)); + return this; + } + + public String pageSize() { + return getFirstQueryOrNull(PAGE_SIZE); + } + + public static class Builder { + + /** + * @see PaginationOptions#pageNumber(int) + */ + public static PaginationOptions pageNumber(Integer pageNumber) { + PaginationOptions options = new PaginationOptions(); + return options.pageNumber(pageNumber); + } + + /** + * @see PaginationOptions#pageSize(int) + */ + public static PaginationOptions pageSize(int pageSize) { + PaginationOptions options = new PaginationOptions(); + return options.pageSize(pageSize); + } + + } +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5bd2a80f/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/ImageApi.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/ImageApi.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/ImageApi.java new file mode 100644 index 0000000..68572ef --- /dev/null +++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/features/ImageApi.java @@ -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.aliyun.ecs.features; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.inject.TypeLiteral; +import org.jclouds.Constants; +import org.jclouds.Fallbacks; +import org.jclouds.aliyun.ecs.ECSComputeServiceApi; +import org.jclouds.aliyun.ecs.domain.Image; +import org.jclouds.aliyun.ecs.domain.Images; +import org.jclouds.aliyun.ecs.domain.options.ListImagesOptions; +import org.jclouds.aliyun.ecs.domain.options.PaginationOptions; +import org.jclouds.aliyun.ecs.filters.FormSign; +import org.jclouds.collect.IterableWithMarker; +import org.jclouds.collect.PagedIterable; +import org.jclouds.collect.internal.Arg0ToPagedIterable; +import org.jclouds.http.functions.ParseJson; +import org.jclouds.json.Json; +import org.jclouds.rest.annotations.Fallback; +import org.jclouds.rest.annotations.QueryParams; +import org.jclouds.rest.annotations.RequestFilters; +import org.jclouds.rest.annotations.ResponseParser; +import org.jclouds.rest.annotations.Transform; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; + +/** + * https://www.alibabacloud.com/help/doc-detail/25534.htm?spm=a2c63.p38356.b99.330.79eb59abhmnMDE + */ +@Consumes(MediaType.APPLICATION_JSON) +@RequestFilters(FormSign.class) +@QueryParams(keys = {"Version", "Format", "SignatureVersion", "ServiceCode", "SignatureMethod"}, + values = {"{" + Constants.PROPERTY_API_VERSION + "}", "JSON", "1.0", "ecs", "HMAC-SHA1"}) +public interface ImageApi { + + @Named("image:list") + @GET + @QueryParams(keys = "Action", values = "DescribeImages") + @ResponseParser(ParseImages.class) + @Fallback(Fallbacks.EmptyIterableWithMarkerOnNotFoundOr404.class) + IterableWithMarker<Image> list(@QueryParam("RegionId") String region, ListImagesOptions options); + + @Named("image:list") + @GET + @QueryParams(keys = "Action", values = "DescribeImages") + @ResponseParser(ParseImages.class) + @Transform(ParseImages.ToPagedIterable.class) + @Fallback(Fallbacks.EmptyPagedIterableOnNotFoundOr404.class) + PagedIterable<Image> list(@QueryParam("RegionId") String region); + + @Singleton + final class ParseImages extends ParseJson<Images> { + + @Inject + ParseImages(final Json json) { + super(json, TypeLiteral.get(Images.class)); + } + + static class ToPagedIterable extends Arg0ToPagedIterable<Image, ToPagedIterable> { + + private final ECSComputeServiceApi api; + + @Inject + ToPagedIterable(ECSComputeServiceApi api) { + this.api = api; + } + + @Override + protected Function<Object, IterableWithMarker<Image>> markerToNextForArg0(final Optional<Object> arg0) { + return new Function<Object, IterableWithMarker<Image>>() { + @Override + public IterableWithMarker<Image> apply(Object input) { + String regionId = arg0.get().toString(); + ListImagesOptions listImagesOptions = ListImagesOptions.Builder.paginationOptions(PaginationOptions.class.cast(input)); + return api.imageApi().list(regionId, listImagesOptions); + } + }; + } + } + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5bd2a80f/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/filters/FormSign.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/filters/FormSign.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/filters/FormSign.java new file mode 100644 index 0000000..ceede09 --- /dev/null +++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/filters/FormSign.java @@ -0,0 +1,134 @@ +/* + * 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.aliyun.ecs.filters; + +import com.google.common.base.Joiner; +import com.google.common.base.Supplier; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.io.ByteProcessor; +import org.jclouds.crypto.Crypto; +import org.jclouds.domain.Credentials; +import org.jclouds.http.HttpException; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpRequestFilter; +import org.jclouds.location.Provider; +import org.jclouds.util.Strings2; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Iterator; +import java.util.Map; +import java.util.SimpleTimeZone; +import java.util.SortedMap; +import java.util.UUID; + +import static com.google.common.base.Charsets.UTF_8; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.io.BaseEncoding.base64; +import static com.google.common.io.ByteStreams.readBytes; +import static org.jclouds.crypto.Macs.asByteProcessor; +import static org.jclouds.http.Uris.uriBuilder; +import static org.jclouds.http.utils.Queries.queryParser; +import static org.jclouds.util.Strings2.toInputStream; + +@Singleton +public class FormSign implements HttpRequestFilter { + + private static final String SEPARATOR = "&"; + public static final String ECS_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'"; + + private final Supplier<Credentials> creds; + private final Crypto crypto; + + @Inject + FormSign(@Provider Supplier<Credentials> creds, Crypto crypto) { + this.creds = creds; + this.crypto = crypto; + } + + public HttpRequest filter(HttpRequest request) throws HttpException { + Credentials currentCreds = checkNotNull(creds.get(), "credential supplier returned null"); + + Multimap<String, String> decodedParams = queryParser().apply(request.getEndpoint().getQuery()); + + SimpleDateFormat df = new SimpleDateFormat(ECS_DATE_FORMAT); + df.setTimeZone(new SimpleTimeZone(0, "GMT")); + + String timestamp = df.format(new Date()); + String signatureNonce = UUID.randomUUID().toString(); + + decodedParams.put("AccessKeyId", currentCreds.identity); + decodedParams.put("Timestamp", timestamp); + decodedParams.put("SignatureNonce", signatureNonce); + + String stringToSign = createStringToSign(request.getMethod(), decodedParams); + + String signature = sign(stringToSign, creds.get().credential); + decodedParams.put("Signature", signature); + + request = request.toBuilder().endpoint(uriBuilder(request.getEndpoint()).query(decodedParams).build()).build(); + return request; + } + + protected String createStringToSign(String method, Multimap<String, String> params) { + + StringBuilder toSign = new StringBuilder(); + toSign.append(method).append(SEPARATOR).append(Strings2.urlEncode("/")).append(SEPARATOR); + toSign.append(getCanonicalizedQueryString(params)); + return toSign.toString(); + } + + /** + * Examines the specified query string parameters and returns a + * canonicalized form. + * <p/> + * The canonicalized query string is formed by first sorting all the query + * string parameters, then URI encoding both the key and value and then + * joining them, in order, separating key value pairs with an '&'. + * + * @return A canonicalized form for the specified query string parameters. + */ + protected String getCanonicalizedQueryString(Multimap<String, String> params) { + SortedMap<String, String> sorted = Maps.newTreeMap(); + if (params == null) { + return ""; + } + Iterator<Map.Entry<String, String>> pairs = params.entries().iterator(); + while (pairs.hasNext()) { + Map.Entry<String, String> pair = pairs.next(); + String key = pair.getKey(); + String value = pair.getValue(); + sorted.put(Strings2.urlEncode(key), Strings2.urlEncode(value)); + } + + return Strings2.urlEncode(Joiner.on("&").withKeyValueSeparator("=").join(sorted)); + } + + public String sign(String toSign, String credentials) { + try { + ByteProcessor<byte[]> hmacSHA1 = asByteProcessor( + crypto.hmacSHA1(String.format("%s&", credentials).getBytes(UTF_8))); + return base64().encode(readBytes(toInputStream(toSign), hmacSHA1)); + } catch (Exception e) { + throw new HttpException("error signing request", e); + } + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5bd2a80f/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/functions/BaseToPagedIterable.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/functions/BaseToPagedIterable.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/functions/BaseToPagedIterable.java new file mode 100644 index 0000000..9f2ddcf --- /dev/null +++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/functions/BaseToPagedIterable.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jclouds.aliyun.ecs.functions; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import org.jclouds.aliyun.ecs.ECSComputeServiceApi; +import org.jclouds.aliyun.ecs.domain.options.ListImagesOptions; +import org.jclouds.collect.IterableWithMarker; +import org.jclouds.collect.internal.Arg0ToPagedIterable; + +/** + * Base class to implement the functions that build the + * <code>PagedIterable</code>. Subclasses just need to override the + * {@link #fetchPageUsingOptions(ListImagesOptions, Optional)} to invoke the right API + * method with the given options parameter to get the next page. + */ +public abstract class BaseToPagedIterable<T, O extends ListImagesOptions> extends + Arg0ToPagedIterable<T, BaseToPagedIterable<T, O>> { + private final Function<Integer, O> pageNumberToOptions; + protected final ECSComputeServiceApi api; + + protected BaseToPagedIterable(ECSComputeServiceApi api, Function<Integer, O> pageNumberToOptions) { + this.api = api; + this.pageNumberToOptions = pageNumberToOptions; + } + + 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 = pageNumberToOptions.apply(Integer.class.cast(input)); + return fetchPageUsingOptions(nextOptions, arg0); + } + }; + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5bd2a80f/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/handlers/ECSComputeServiceErrorHandler.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/handlers/ECSComputeServiceErrorHandler.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/handlers/ECSComputeServiceErrorHandler.java new file mode 100644 index 0000000..660a880 --- /dev/null +++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/handlers/ECSComputeServiceErrorHandler.java @@ -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.aliyun.ecs.handlers; + +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.ResourceNotFoundException; + +import javax.inject.Singleton; + +import static org.jclouds.http.HttpUtils.closeClientButKeepContentStream; + +/** + * This will parse and set an appropriate exception on the command object. + */ +@Singleton +public class ECSComputeServiceErrorHandler 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: + exception = new IllegalArgumentException(message, exception); + break; + case 401: + case 403: + 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; + } + command.setException(exception); + } +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5bd2a80f/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/ECSComputeProviderMetadataTest.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/ECSComputeProviderMetadataTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/ECSComputeProviderMetadataTest.java new file mode 100644 index 0000000..f663b77 --- /dev/null +++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/ECSComputeProviderMetadataTest.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jclouds.aliyun.ecs.compute; + +import org.jclouds.aliyun.ecs.ECSComputeServiceProviderMetadata; +import org.jclouds.aliyun.ecs.ECSServiceApiMetadata; +import org.jclouds.providers.internal.BaseProviderMetadataTest; +import org.testng.annotations.Test; + +@Test(groups = "unit", testName = "ECSComputeProviderMetadataTest") +public class ECSComputeProviderMetadataTest extends BaseProviderMetadataTest { + + public ECSComputeProviderMetadataTest() { + super(new ECSComputeServiceProviderMetadata(), new ECSServiceApiMetadata()); + } +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5bd2a80f/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/ImageApiLiveTest.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/ImageApiLiveTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/ImageApiLiveTest.java new file mode 100644 index 0000000..957d9cc --- /dev/null +++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/ImageApiLiveTest.java @@ -0,0 +1,50 @@ +/* + * 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.aliyun.ecs.compute.features; + +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import org.jclouds.aliyun.ecs.compute.internal.BaseECSComputeServiceApiLiveTest; +import org.jclouds.aliyun.ecs.domain.Image; +import org.jclouds.aliyun.ecs.domain.Regions; +import org.jclouds.aliyun.ecs.features.ImageApi; +import org.testng.annotations.Test; + +import java.util.concurrent.atomic.AtomicInteger; + +import static org.testng.Assert.assertTrue; +import static org.testng.util.Strings.isNullOrEmpty; + +@Test(groups = "live", testName = "ImageApiLiveTest") +public class ImageApiLiveTest extends BaseECSComputeServiceApiLiveTest { + + public void testList() { + final AtomicInteger found = new AtomicInteger(0); + assertTrue(Iterables.all(api().list(Regions.EU_CENTRAL_1.getName()).concat(), new Predicate<Image>() { + @Override + public boolean apply(Image input) { + found.incrementAndGet(); + return !isNullOrEmpty(input.imageId()); + } + }), "All images must have the 'id' field populated"); + assertTrue(found.get() > 0, "Expected some image to be returned"); + } + + private ImageApi api() { + return api.imageApi(); + } +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5bd2a80f/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/ImageApiMockTest.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/ImageApiMockTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/ImageApiMockTest.java new file mode 100644 index 0000000..a29c067 --- /dev/null +++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/features/ImageApiMockTest.java @@ -0,0 +1,79 @@ +/* + * 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.aliyun.ecs.compute.features; + +import org.jclouds.aliyun.ecs.compute.internal.BaseECSComputeServiceApiMockTest; +import org.jclouds.aliyun.ecs.domain.Image; +import org.jclouds.aliyun.ecs.domain.Regions; +import org.jclouds.aliyun.ecs.domain.options.ListImagesOptions; +import org.jclouds.aliyun.ecs.domain.options.PaginationOptions; +import org.jclouds.collect.IterableWithMarker; +import org.testng.annotations.Test; + +import static com.google.common.collect.Iterables.isEmpty; +import static com.google.common.collect.Iterables.size; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +@Test(groups = "unit", testName = "ImageApiMockTest", singleThreaded = true) +public class ImageApiMockTest extends BaseECSComputeServiceApiMockTest { + + public void testListImages() throws InterruptedException { + server.enqueue(jsonResponse("/images-first.json")); + server.enqueue(jsonResponse("/images-second.json")); + server.enqueue(jsonResponse("/images-last.json")); + + Iterable<Image> images = api.imageApi().list(Regions.EU_CENTRAL_1.getName()).concat(); + assertEquals(size(images), 28); // Force the PagedIterable to advance + assertEquals(server.getRequestCount(), 3); + assertSent(server, "GET", "DescribeImages"); + assertSent(server, "GET", "DescribeImages", 2); + assertSent(server, "GET", "DescribeImages", 3); + } + + public void testListImagesReturns404() { + server.enqueue(response404()); + Iterable<Image> images = api.imageApi().list(Regions.EU_CENTRAL_1.getName()).concat(); + assertTrue(isEmpty(images)); + assertEquals(server.getRequestCount(), 1); + } + + public void testListImagesWithOptions() throws InterruptedException { + server.enqueue(jsonResponse("/images-first.json")); + + IterableWithMarker<Image> images = api.imageApi().list(Regions.EU_CENTRAL_1.getName(), ListImagesOptions.Builder + .paginationOptions(PaginationOptions.Builder.pageNumber(1))); + + assertEquals(size(images), 10); + assertEquals(server.getRequestCount(), 1); + + assertSent(server, "GET", "DescribeImages", 1); + } + + public void testListImagesWithOptionsReturns404() throws InterruptedException { + server.enqueue(response404()); + + IterableWithMarker<Image> images = api.imageApi().list(Regions.EU_CENTRAL_1.getName(), ListImagesOptions.Builder + .paginationOptions(PaginationOptions.Builder.pageNumber(2))); + + assertTrue(isEmpty(images)); + + assertEquals(server.getRequestCount(), 1); + assertSent(server, "GET", "DescribeImages", 2); + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5bd2a80f/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/internal/BaseECSComputeServiceApiLiveTest.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/internal/BaseECSComputeServiceApiLiveTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/internal/BaseECSComputeServiceApiLiveTest.java new file mode 100644 index 0000000..f7b9526 --- /dev/null +++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/internal/BaseECSComputeServiceApiLiveTest.java @@ -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.aliyun.ecs.compute.internal; + +import com.google.inject.Injector; +import com.google.inject.Module; +import org.jclouds.aliyun.ecs.ECSComputeServiceApi; +import org.jclouds.apis.BaseApiLiveTest; +import org.jclouds.compute.config.ComputeServiceProperties; + +import java.util.Properties; +import java.util.concurrent.TimeUnit; + +public class BaseECSComputeServiceApiLiveTest extends BaseApiLiveTest<ECSComputeServiceApi> { + + public BaseECSComputeServiceApiLiveTest() { + provider = "aliyun-ecs"; + } + + @Override + protected Properties setupProperties() { + Properties props = super.setupProperties(); + props.put(ComputeServiceProperties.POLL_INITIAL_PERIOD, 1000); + props.put(ComputeServiceProperties.POLL_MAX_PERIOD, 10000); + props.put(ComputeServiceProperties.TIMEOUT_IMAGE_AVAILABLE, TimeUnit.MINUTES.toMillis(45)); + return props; + } + + @Override + protected ECSComputeServiceApi create(Properties props, Iterable<Module> modules) { + Injector injector = newBuilder().modules(modules).overrides(props).buildInjector(); + return injector.getInstance(ECSComputeServiceApi.class); + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5bd2a80f/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/internal/BaseECSComputeServiceApiMockTest.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/internal/BaseECSComputeServiceApiMockTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/internal/BaseECSComputeServiceApiMockTest.java new file mode 100644 index 0000000..5845339 --- /dev/null +++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/internal/BaseECSComputeServiceApiMockTest.java @@ -0,0 +1,118 @@ +/* + * 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.aliyun.ecs.compute.internal; + +import com.google.common.base.Charsets; +import com.google.common.base.Splitter; +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableSet; +import com.google.common.io.Resources; +import com.google.inject.Module; +import com.squareup.okhttp.mockwebserver.MockResponse; +import com.squareup.okhttp.mockwebserver.MockWebServer; +import com.squareup.okhttp.mockwebserver.RecordedRequest; +import org.jclouds.ContextBuilder; +import org.jclouds.aliyun.ecs.ECSComputeServiceApi; +import org.jclouds.aliyun.ecs.ECSComputeServiceProviderMetadata; +import org.jclouds.concurrent.config.ExecutorServiceModule; +import org.jclouds.json.Json; +import org.jclouds.rest.ApiContext; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; + +import java.io.IOException; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import static com.google.common.util.concurrent.MoreExecutors.newDirectExecutorService; +import static org.jclouds.Constants.PROPERTY_MAX_RETRIES; +import static org.testng.Assert.assertEquals; + +public class BaseECSComputeServiceApiMockTest { + + private static final String DEFAULT_ENDPOINT = new ECSComputeServiceProviderMetadata().getEndpoint(); + + private final Set<Module> modules = ImmutableSet.<Module>of(new ExecutorServiceModule(newDirectExecutorService())); + protected MockWebServer server; + protected ECSComputeServiceApi api; + private Json json; + private ApiContext<ECSComputeServiceApi> ctx; + + @BeforeMethod + public void start() throws IOException { + server = new MockWebServer(); + server.play(); + ctx = ContextBuilder.newBuilder("aliyun-ecs").credentials("user", "password").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() { + Properties properties = new Properties(); + properties.put(PROPERTY_MAX_RETRIES, "0"); // Do not retry + return 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 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 RecordedRequest assertSent(MockWebServer server, String method, String action) throws InterruptedException { + RecordedRequest request = server.takeRequest(); + assertEquals(request.getMethod(), method); + Map<String, String> queryParameters = Splitter.on('&').trimResults().withKeyValueSeparator("=").split(request.getPath()); + assertEquals(queryParameters.get("Action"), action); + assertEquals(request.getHeader("Accept"), "application/json"); + return request; + } + + protected RecordedRequest assertSent(MockWebServer server, String method, String action, Integer page) throws InterruptedException { + RecordedRequest request = server.takeRequest(); + assertEquals(request.getMethod(), method); + Map<String, String> queryParameters = Splitter.on('&').trimResults().withKeyValueSeparator("=").split(request.getPath()); + assertEquals(queryParameters.get("Action"), action); + assertEquals(Integer.valueOf(queryParameters.get("pageNumber")), page); + assertEquals(request.getHeader("Accept"), "application/json"); + return request; + } + +}
