[JCLOUDS-1430] Aliyun ECS - add instance API - add compute abstraction - add validation for vpc and vSwitch IDs - add builders for Image and Instance - add unit tests for compute/functions - add pagination to instanceStatus api - rename provider id - clean up code - add network apis - vpc api + tests - vswitch api + tests - improve CreateResourcesThenCreateNodes - create default vpc and vswitch in case needed - fix InstanceApiLiveTest - add ECSDependencyViolationRetryHandler - add ErrorRetryHandler - fix ListImagesOptions.imageId - fix enums in Instance and EIPAddress
Project: http://git-wip-us.apache.org/repos/asf/jclouds-labs/repo Commit: http://git-wip-us.apache.org/repos/asf/jclouds-labs/commit/2c7db7e8 Tree: http://git-wip-us.apache.org/repos/asf/jclouds-labs/tree/2c7db7e8 Diff: http://git-wip-us.apache.org/repos/asf/jclouds-labs/diff/2c7db7e8 Branch: refs/heads/master Commit: 2c7db7e809085a7738d76f5714663231dff0a9f3 Parents: a5dbf00 Author: andreaturli <[email protected]> Authored: Tue Jul 3 14:47:43 2018 +0200 Committer: Andrea Turli <[email protected]> Committed: Tue Sep 11 21:19:59 2018 -0700 ---------------------------------------------------------------------- aliyun-ecs/README.md | 43 + aliyun-ecs/pom.xml | 3 - .../aliyun/ecs/ECSComputeServiceApi.java | 11 + .../ecs/ECSComputeServiceProviderMetadata.java | 4 +- .../aliyun/ecs/ECSServiceApiMetadata.java | 6 +- .../aliyun/ecs/compute/ECSComputeService.java | 154 + .../ecs/compute/ECSComputeServiceAdapter.java | 278 ++ .../compute/config/ECSServiceContextModule.java | 160 + .../compute/functions/ImageInRegionToImage.java | 104 + .../functions/InstanceStatusToStatus.java | 44 + .../functions/InstanceToNodeMetadata.java | 126 + .../functions/InstanceTypeToHardware.java | 49 + .../ecs/compute/functions/RegionToLocation.java | 54 + .../functions/internal/OperatingSystems.java | 51 + .../options/ECSServiceTemplateOptions.java | 180 + .../ecs/compute/strategy/CleanupResources.java | 112 + .../CreateResourcesThenCreateNodes.java | 360 ++ .../config/ECSComputeServiceHttpApiModule.java | 24 + .../domain/AllocatePublicIpAddressRequest.java | 59 + .../aliyun/ecs/domain/AvailableResource.java | 43 + .../aliyun/ecs/domain/AvailableZone.java | 47 + .../ecs/domain/DedicatedHostAttribute.java | 37 + .../jclouds/aliyun/ecs/domain/EipAddress.java | 67 + .../jclouds/aliyun/ecs/domain/ErrorMessage.java | 39 + .../org/jclouds/aliyun/ecs/domain/Image.java | 112 +- .../org/jclouds/aliyun/ecs/domain/Instance.java | 321 ++ .../aliyun/ecs/domain/InstanceRequest.java | 59 + .../aliyun/ecs/domain/InstanceStatus.java | 55 + .../jclouds/aliyun/ecs/domain/InstanceType.java | 85 + .../aliyun/ecs/domain/NetworkInterface.java | 41 + .../jclouds/aliyun/ecs/domain/Permission.java | 2 +- .../jclouds/aliyun/ecs/domain/ResourceType.java | 42 + .../aliyun/ecs/domain/SecurityGroup.java | 14 +- .../aliyun/ecs/domain/SupportedResource.java | 52 + .../java/org/jclouds/aliyun/ecs/domain/Tag.java | 14 +- .../org/jclouds/aliyun/ecs/domain/UserCidr.java | 27 + .../java/org/jclouds/aliyun/ecs/domain/VPC.java | 131 + .../jclouds/aliyun/ecs/domain/VPCRequest.java | 74 + .../org/jclouds/aliyun/ecs/domain/VSwitch.java | 112 + .../aliyun/ecs/domain/VSwitchRequest.java | 58 + .../aliyun/ecs/domain/VpcAttributes.java | 48 + .../domain/internal/PaginatedCollection.java | 4 +- .../domain/options/CreateInstanceOptions.java | 161 + .../ecs/domain/options/CreateVPCOptions.java | 106 + .../domain/options/CreateVSwitchOptions.java | 74 + .../ecs/domain/options/ListImagesOptions.java | 6 +- .../options/ListInstanceStatusOptions.java | 50 + .../domain/options/ListInstancesOptions.java | 239 + .../ecs/domain/options/ListVPCsOptions.java | 63 + .../domain/options/ListVSwitchesOptions.java | 89 + .../aliyun/ecs/domain/options/TagOptions.java | 22 +- .../ecs/domain/regionscoped/ImageInRegion.java | 36 + .../ecs/domain/regionscoped/RegionAndId.java | 54 + .../aliyun/ecs/features/InstanceApi.java | 264 ++ .../aliyun/ecs/features/SecurityGroupApi.java | 3 + .../aliyun/ecs/features/SshKeyPairApi.java | 4 + .../org/jclouds/aliyun/ecs/features/TagApi.java | 10 +- .../org/jclouds/aliyun/ecs/features/VPCApi.java | 140 + .../jclouds/aliyun/ecs/features/VSwitchApi.java | 145 + .../ecs/functions/PutStringInDoubleQuotes.java | 27 + .../ecs/handlers/ECSErrorRetryHandler.java | 74 + .../ecs/predicates/InstanceStatusPredicate.java | 33 + .../ecs/compute/ECSComputeServiceLiveTest.java | 46 + .../ecs/compute/ECSTemplateBuilderLiveTest.java | 53 + .../ecs/compute/features/ImageApiLiveTest.java | 13 +- .../ecs/compute/features/ImageApiMockTest.java | 19 +- .../compute/features/InstanceApiLiveTest.java | 189 + .../compute/features/InstanceApiMockTest.java | 168 + .../features/RegionAndZoneApiMockTest.java | 9 +- .../features/SecurityGroupApiLiveTest.java | 11 +- .../features/SecurityGroupApiMockTest.java | 19 +- .../compute/features/SshKeyPairApiLiveTest.java | 13 +- .../compute/features/SshKeyPairApiMockTest.java | 21 +- .../ecs/compute/features/TagApiLiveTest.java | 16 +- .../ecs/compute/features/TagApiMockTest.java | 19 +- .../ecs/compute/features/VPCApiLiveTest.java | 89 + .../ecs/compute/features/VPCApiMockTest.java | 87 + .../compute/features/VSwitchApiLiveTest.java | 100 + .../compute/features/VSwitchApiMockTest.java | 87 + .../functions/ImageInRegionToImageTest.java | 141 + .../functions/InstanceStatusToStatusTest.java | 58 + .../functions/InstanceToHardwareTest.java | 61 + .../functions/InstanceToNodeMetadataTest.java | 249 + .../BaseECSComputeServiceApiLiveTest.java | 22 +- .../BaseECSComputeServiceApiMockTest.java | 5 +- .../CreateResourcesThenCreateNodesTest.java | 395 ++ .../src/test/resources/availableZones.json | 423 ++ .../src/test/resources/instanceStatus.json | 18 + .../src/test/resources/instanceTypes.json | 4281 ++++++++++++++++++ .../src/test/resources/instances-first.json | 960 ++++ .../src/test/resources/instances-last.json | 960 ++++ aliyun-ecs/src/test/resources/logback-test.xml | 42 + .../src/test/resources/vpc-create-res.json | 6 + .../src/test/resources/vpc-delete-res.json | 3 + aliyun-ecs/src/test/resources/vpcs-first.json | 29 + aliyun-ecs/src/test/resources/vpcs-last.json | 29 + .../src/test/resources/vswitch-create-res.json | 4 + .../src/test/resources/vswitch-delete-res.json | 3 + .../src/test/resources/vswitches-first.json | 22 + .../src/test/resources/vswitches-last.json | 22 + 100 files changed, 13485 insertions(+), 113 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/README.md ---------------------------------------------------------------------- diff --git a/aliyun-ecs/README.md b/aliyun-ecs/README.md new file mode 100644 index 0000000..5bb8723 --- /dev/null +++ b/aliyun-ecs/README.md @@ -0,0 +1,43 @@ +alibaba Elastic Compute Service Provider +========================== + +# How to use it + +alibaba ECS provider works exactly as any other jclouds provider. +Notice that as alibaba supports dozens of locations and to limit the scope of some operations, one may want to use: + +and +```bash +jclouds.regions +``` +which is by default `null`. If you want to target only the `north europe` region, you can use + +```bash +jclouds.regions="eu-central-1" +``` + +# Setting Up Test Environment + +Get or create the `User Access Key` and `Access Key Secret` for your account at `https://usercenter.console.alibaba.com/#/manage/ak` + +# Run Live Tests + +Use the following to run one live test: + +```bash +mvn -Dtest=<name of the live test> \ + -Dtest.alibaba-ecs.identity="<AccessKey ID>" \ + -Dtest.alibaba-ecs.credential="<Access Key Secret>" + integration-test -Plive +``` + +Use the following to run all the live tests: + +```bash + +mvn clean verify -Plive \ + -Dtest.alibaba-ecs.identity="<AccessKey ID>" \ + -Dtest.alibaba-ecs.credential="<Access Key Secret>" +``` + + http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/pom.xml ---------------------------------------------------------------------- diff --git a/aliyun-ecs/pom.xml b/aliyun-ecs/pom.xml index da1b900..7b81aaf 100644 --- a/aliyun-ecs/pom.xml +++ b/aliyun-ecs/pom.xml @@ -32,8 +32,6 @@ <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/> @@ -142,6 +140,5 @@ </profile> </profiles> - </project> http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/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 index bb24cf0..2b0c9f8 100644 --- a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSComputeServiceApi.java +++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSComputeServiceApi.java @@ -17,10 +17,13 @@ package org.jclouds.aliyun.ecs; import org.jclouds.aliyun.ecs.features.ImageApi; +import org.jclouds.aliyun.ecs.features.InstanceApi; import org.jclouds.aliyun.ecs.features.RegionAndZoneApi; import org.jclouds.aliyun.ecs.features.SecurityGroupApi; import org.jclouds.aliyun.ecs.features.SshKeyPairApi; import org.jclouds.aliyun.ecs.features.TagApi; +import org.jclouds.aliyun.ecs.features.VPCApi; +import org.jclouds.aliyun.ecs.features.VSwitchApi; import org.jclouds.rest.annotations.Delegate; import java.io.Closeable; @@ -42,4 +45,12 @@ public interface ECSComputeServiceApi extends Closeable { @Delegate TagApi tagApi(); + @Delegate + InstanceApi instanceApi(); + + @Delegate + VPCApi vpcApi(); + + @Delegate + VSwitchApi vSwitchApi(); } http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/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 index fbd7206..3ffed6d 100644 --- a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSComputeServiceProviderMetadata.java +++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSComputeServiceProviderMetadata.java @@ -51,8 +51,8 @@ public class ECSComputeServiceProviderMetadata extends BaseProviderMetadata { public static class Builder extends BaseProviderMetadata.Builder { protected Builder() { - id("aliyun-ecs") - .name("Alibaba Elastic Compute Service") + id("alibaba-ecs") + .name("Alibaba Cloud Elastic Compute Service") .apiMetadata(new ECSServiceApiMetadata()) .homepage(URI.create("https://www.alibabacloud.com")) .console(URI.create("https://ecs.console.aliyun.com")) http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/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 index d81ef96..54e725c 100644 --- a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSServiceApiMetadata.java +++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/ECSServiceApiMetadata.java @@ -18,6 +18,7 @@ package org.jclouds.aliyun.ecs; import com.google.common.collect.ImmutableSet; import com.google.inject.Module; +import org.jclouds.aliyun.ecs.compute.config.ECSServiceContextModule; import org.jclouds.aliyun.ecs.config.ECSComputeServiceHttpApiModule; import org.jclouds.aliyun.ecs.config.ECSComputeServiceParserModule; import org.jclouds.apis.ApiMetadata; @@ -46,7 +47,7 @@ public class ECSServiceApiMetadata extends BaseHttpApiMetadata<ECSComputeService public static Properties defaultProperties() { Properties properties = BaseHttpApiMetadata.defaultProperties(); - properties.put(TEMPLATE, "osFamily=CENTOS,os64Bit=true,osVersionMatches=7.4"); + properties.put(TEMPLATE, "osFamily=CENTOS,os64Bit=true,osVersionMatches=7.*"); properties.put(TIMEOUT_NODE_RUNNING, 900000); // 15 mins properties.put(TIMEOUT_NODE_SUSPENDED, 900000); // 15 mins return properties; @@ -60,7 +61,7 @@ public class ECSServiceApiMetadata extends BaseHttpApiMetadata<ECSComputeService public static class Builder extends BaseHttpApiMetadata.Builder<ECSComputeServiceApi, Builder> { protected Builder() { - id("aliyun-ecs") + id("alibaba-ecs") .name("Alibaba Elastic Compute Service API") .identityName("user name") .credentialName("user password") @@ -72,6 +73,7 @@ public class ECSServiceApiMetadata extends BaseHttpApiMetadata<ECSComputeService .defaultModules(ImmutableSet.<Class<? extends Module>>builder() .add(ECSComputeServiceHttpApiModule.class) .add(ECSComputeServiceParserModule.class) + .add(ECSServiceContextModule.class) .build()); } http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/ECSComputeService.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/ECSComputeService.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/ECSComputeService.java new file mode 100644 index 0000000..b3c6802 --- /dev/null +++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/ECSComputeService.java @@ -0,0 +1,154 @@ +/* + * 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 com.google.common.base.Optional; +import com.google.common.base.Predicate; +import com.google.common.base.Supplier; +import com.google.common.collect.Iterables; +import com.google.common.util.concurrent.ListeningExecutorService; +import org.jclouds.Constants; +import org.jclouds.aliyun.ecs.ECSComputeServiceApi; +import org.jclouds.aliyun.ecs.compute.strategy.CleanupResources; +import org.jclouds.aliyun.ecs.domain.SecurityGroup; +import org.jclouds.aliyun.ecs.domain.VSwitch; +import org.jclouds.aliyun.ecs.domain.options.ListVSwitchesOptions; +import org.jclouds.aliyun.ecs.domain.regionscoped.RegionAndId; +import org.jclouds.collect.Memoized; +import org.jclouds.compute.ComputeServiceContext; +import org.jclouds.compute.callables.RunScriptOnNode; +import org.jclouds.compute.domain.Hardware; +import org.jclouds.compute.domain.Image; +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.domain.TemplateBuilder; +import org.jclouds.compute.extensions.ImageExtension; +import org.jclouds.compute.extensions.SecurityGroupExtension; +import org.jclouds.compute.extensions.internal.DelegatingImageExtension; +import org.jclouds.compute.internal.BaseComputeService; +import org.jclouds.compute.internal.PersistNodeCredentials; +import org.jclouds.compute.options.TemplateOptions; +import org.jclouds.compute.strategy.CreateNodesInGroupThenAddToSet; +import org.jclouds.compute.strategy.DestroyNodeStrategy; +import org.jclouds.compute.strategy.GetImageStrategy; +import org.jclouds.compute.strategy.GetNodeMetadataStrategy; +import org.jclouds.compute.strategy.InitializeRunScriptOnNodeOrPlaceInBadMap; +import org.jclouds.compute.strategy.ListNodesStrategy; +import org.jclouds.compute.strategy.RebootNodeStrategy; +import org.jclouds.compute.strategy.ResumeNodeStrategy; +import org.jclouds.compute.strategy.SuspendNodeStrategy; +import org.jclouds.domain.Credentials; +import org.jclouds.domain.Location; +import org.jclouds.scriptbuilder.functions.InitAdminAccess; + +import javax.annotation.Nullable; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Provider; +import javax.inject.Singleton; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; + +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; + +@Singleton +public class ECSComputeService extends BaseComputeService { + private final CleanupResources cleanupResources; + + @Inject + protected ECSComputeService(ComputeServiceContext context, Map<String, Credentials> credentialStore, + @Memoized Supplier<Set<? extends Image>> images, @Memoized Supplier<Set<? extends Hardware>> sizes, + @Memoized Supplier<Set<? extends Location>> locations, ListNodesStrategy listNodesStrategy, + GetImageStrategy getImageStrategy, GetNodeMetadataStrategy getNodeMetadataStrategy, + CreateNodesInGroupThenAddToSet runNodesAndAddToSetStrategy, RebootNodeStrategy rebootNodeStrategy, + DestroyNodeStrategy destroyNodeStrategy, ResumeNodeStrategy startNodeStrategy, + SuspendNodeStrategy stopNodeStrategy, Provider<TemplateBuilder> templateBuilderProvider, + @Named("DEFAULT") Provider<TemplateOptions> templateOptionsProvider, + @Named(TIMEOUT_NODE_RUNNING) Predicate<AtomicReference<NodeMetadata>> nodeRunning, + @Named(TIMEOUT_NODE_TERMINATED) Predicate<AtomicReference<NodeMetadata>> nodeTerminated, + @Named(TIMEOUT_NODE_SUSPENDED) Predicate<AtomicReference<NodeMetadata>> nodeSuspended, + InitializeRunScriptOnNodeOrPlaceInBadMap.Factory initScriptRunnerFactory, + RunScriptOnNode.Factory runScriptOnNodeFactory, InitAdminAccess initAdminAccess, + PersistNodeCredentials persistNodeCredentials, + @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor, + CleanupResources cleanupResources, Optional<ImageExtension> imageExtension, + Optional<SecurityGroupExtension> securityGroupExtension, + DelegatingImageExtension.Factory delegatingImageExtension) { + super(context, credentialStore, images, sizes, locations, listNodesStrategy, getImageStrategy, + getNodeMetadataStrategy, runNodesAndAddToSetStrategy, rebootNodeStrategy, destroyNodeStrategy, + startNodeStrategy, stopNodeStrategy, templateBuilderProvider, templateOptionsProvider, nodeRunning, + nodeTerminated, nodeSuspended, initScriptRunnerFactory, initAdminAccess, runScriptOnNodeFactory, + persistNodeCredentials, userExecutor, imageExtension, securityGroupExtension, delegatingImageExtension); + this.cleanupResources = cleanupResources; + } + + @Override + protected void cleanUpIncidentalResourcesOfDeadNodes(Set<? extends NodeMetadata> deadNodes) { + for (NodeMetadata deadNode : deadNodes) { + RegionAndId regionAndId = RegionAndId.fromSlashEncoded(deadNode.getId()); + Set<String> tags = deadNode.getTags(); + String vSwitchId = extractVSwitchId(tags); + VSwitch vSwitch = context.unwrapApi(ECSComputeServiceApi.class).vSwitchApi().list(deadNode.getLocation().getId(), ListVSwitchesOptions.Builder.vSwitchId(vSwitchId)).first().orNull(); + String vpcId = vSwitch.vpcId(); + + try { + cleanupResources.cleanupNode(regionAndId); + } catch (Exception ex) { + logger.warn(ex, "Error cleaning up resources for node %s", deadNode); + } + + List<SecurityGroup> securityGroups = cleanupResources.findOrphanedSecurityGroups(regionAndId.regionId(), deadNode.getGroup()); + for (SecurityGroup securityGroup : securityGroups) { + logger.debug(">> destroying security group %s ...", securityGroup.id()); + if (cleanupResources.cleanupSecurityGroupIfOrphaned(regionAndId.regionId(), securityGroup.id())) { + logger.debug(">> security group: (%s) has been deleted.", securityGroup.id()); + } else { + logger.warn(">> security group: (%s) has not been deleted.", securityGroup.id()); + } + } + + // FIXME not sure it is correct to always delete vSwitch and VPC_PREFIX + logger.debug(">> destroying vSwitch %s ...", vSwitchId); + if (cleanupResources.cleanupVSwitchIfOrphaned(regionAndId.regionId(), vSwitchId)) { + logger.debug(">> vSwitch: (%s) has been deleted.", vSwitchId); + } else { + logger.warn(">> vSwitch: (%s) has not been deleted.", vSwitchId); + } + + logger.debug(">> destroying vpc %s ...", vpcId); + try { + cleanupResources.cleanupVPCIfOrphaned(regionAndId.regionId(), vpcId); + logger.debug(">> VPC_PREFIX: (%s) has been deleted.", vpcId); + } catch (IllegalArgumentException e) { + logger.warn(">> VPC_PREFIX: (%s) has not been deleted.", vpcId); + } + } + } + + private String extractVSwitchId(Set<String> tags) { + String vSwitchIdTag = Iterables.tryFind(tags, new Predicate<String>() { + @Override + public boolean apply(@Nullable String input) { + return input.startsWith("vsw-"); + } + }).orNull(); + return vSwitchIdTag; + } +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/ECSComputeServiceAdapter.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/ECSComputeServiceAdapter.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/ECSComputeServiceAdapter.java new file mode 100644 index 0000000..7e12ed8 --- /dev/null +++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/ECSComputeServiceAdapter.java @@ -0,0 +1,278 @@ +/* + * 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 com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.base.Supplier; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import org.jclouds.aliyun.ecs.ECSComputeServiceApi; +import org.jclouds.aliyun.ecs.compute.strategy.CleanupResources; +import org.jclouds.aliyun.ecs.domain.AvailableResource; +import org.jclouds.aliyun.ecs.domain.AvailableZone; +import org.jclouds.aliyun.ecs.domain.Image; +import org.jclouds.aliyun.ecs.domain.Instance; +import org.jclouds.aliyun.ecs.domain.InstanceRequest; +import org.jclouds.aliyun.ecs.domain.InstanceType; +import org.jclouds.aliyun.ecs.domain.Region; +import org.jclouds.aliyun.ecs.domain.SupportedResource; +import org.jclouds.aliyun.ecs.domain.options.CreateInstanceOptions; +import org.jclouds.aliyun.ecs.domain.options.ListImagesOptions; +import org.jclouds.aliyun.ecs.domain.options.ListInstancesOptions; +import org.jclouds.aliyun.ecs.domain.options.TagOptions; +import org.jclouds.aliyun.ecs.domain.regionscoped.ImageInRegion; +import org.jclouds.aliyun.ecs.domain.regionscoped.RegionAndId; +import org.jclouds.aliyun.ecs.compute.options.ECSServiceTemplateOptions; +import org.jclouds.compute.ComputeServiceAdapter; +import org.jclouds.compute.domain.Template; +import org.jclouds.compute.reference.ComputeServiceConstants; +import org.jclouds.compute.util.ComputeServiceUtils; +import org.jclouds.logging.Logger; + +import javax.annotation.Nullable; +import javax.annotation.Resource; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.Iterables.contains; +import static com.google.common.collect.Lists.newArrayList; +import static java.lang.String.format; +import static org.jclouds.aliyun.ecs.domain.regionscoped.RegionAndId.fromSlashEncoded; +import static org.jclouds.aliyun.ecs.domain.regionscoped.RegionAndId.slashEncodeRegionAndId; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED; + +/** + * defines the connection between the {@link ECSComputeServiceApi} implementation and + * the jclouds {@link org.jclouds.compute.ComputeService} + */ +@Singleton +public class ECSComputeServiceAdapter implements ComputeServiceAdapter<Instance, InstanceType, ImageInRegion, Region> { + + private final ECSComputeServiceApi api; + private final Predicate<String> instanceSuspendedPredicate; + + private final Supplier<Set<String>> regionIds; + private final CleanupResources cleanupResources; + + @Resource + @Named(ComputeServiceConstants.COMPUTE_LOGGER) + protected Logger logger = Logger.NULL; + + @Inject + ECSComputeServiceAdapter(ECSComputeServiceApi api, + @Named(TIMEOUT_NODE_SUSPENDED) Predicate<String> instanceSuspendedPredicate, + @org.jclouds.location.Region Supplier<Set<String>> regionIds, + CleanupResources cleanupResources) { + this.api = api; + this.instanceSuspendedPredicate = instanceSuspendedPredicate; + this.regionIds = regionIds; + this.cleanupResources = cleanupResources; + } + + @Override + public NodeAndInitialCredentials<Instance> createNodeWithGroupEncodedIntoName(String group, String name, Template template) { + String instanceType = template.getHardware().getId(); + String regionId = template.getLocation().getId(); + String imageId = template.getImage().getId(); + + ECSServiceTemplateOptions templateOptions = template.getOptions().as(ECSServiceTemplateOptions.class); + + String keyPairName = templateOptions.getKeyPairName(); + String securityGroupId = Iterables.getOnlyElement(templateOptions.getGroups()); + String vSwitchId = templateOptions.getVSwitchId(); + Instance.InternetChargeType internetChargeType = Instance.InternetChargeType.fromValue(templateOptions.getInternetChargeType()); + int internetMaxBandwidthOut = templateOptions.getInternetMaxBandwidthOut(); + String instanceChargeType = templateOptions.getInstanceChargeType(); + + Map<String, String> tags = ComputeServiceUtils.metadataAndTagsAsValuesOfEmptyString(templateOptions); + tags = new ImmutableMap.Builder() + .putAll(tags) + .put(vSwitchId, "") + .build(); + TagOptions tagOptions = TagOptions.Builder.tags(tags); + + InstanceRequest instanceRequest = api.instanceApi().create(regionId, RegionAndId.fromSlashEncoded(imageId).id(), securityGroupId, name, instanceType, + CreateInstanceOptions.Builder + .vSwitchId(vSwitchId) + .internetChargeType(internetChargeType.toString()) + .internetMaxBandwidthOut(internetMaxBandwidthOut) + .instanceChargeType(instanceChargeType) + .instanceName(name) + .keyPairName(keyPairName) + .tagOptions(tagOptions) + ); + + String regionAndInstanceId = slashEncodeRegionAndId(regionId, instanceRequest.getInstanceId()); + if (!instanceSuspendedPredicate.apply(regionAndInstanceId)) { + final String message = format("Instance %s was not created correctly. The associated resources created for it will be destroyed", instanceRequest.getInstanceId()); + logger.warn(message); + cleanupResources.cleanupNode(RegionAndId.create(regionId, instanceRequest.getInstanceId())); + cleanupResources.cleanupSecurityGroupIfOrphaned(regionId, securityGroupId); + } + + api.instanceApi().allocatePublicIpAddress(regionId, instanceRequest.getInstanceId()); + api.instanceApi().powerOn(instanceRequest.getInstanceId()); + Instance instance = Iterables.get(api.instanceApi().list(regionId, ListInstancesOptions.Builder.instanceIds(instanceRequest.getInstanceId())), 0); + + // Safe to pass null credentials here, as jclouds will default populate + // the node with the default credentials from the image, or the ones in + // the options, if provided. + return new NodeAndInitialCredentials(instance, + slashEncodeRegionAndId(regionId, instanceRequest.getInstanceId()), null); + } + + @Override + public Iterable<InstanceType> listHardwareProfiles() { + final ImmutableSet.Builder<String> instanceTypeIdsBuilder = ImmutableSet.builder(); + for (String regionId : getAvailableLocationNames()) { + instanceTypeIdsBuilder.addAll(getInstanceTypeIds(regionId)); + } + final Set<String> ids = instanceTypeIdsBuilder.build(); + + List<InstanceType> instanceTypes = FluentIterable.from(api.instanceApi().listTypes()) + .filter(new Predicate<InstanceType>() { + @Override + public boolean apply(@Nullable InstanceType input) { + return contains(ids, input.id()); + } + }).toList(); + + return instanceTypes; + } + + private List<String> getInstanceTypeIds(String regionId) { + List<String> instanceTypeIds = Lists.newArrayList(); + for (AvailableZone availableZone : api.instanceApi().listInstanceTypesByAvailableZone(regionId)) { + for (AvailableResource availableResource : availableZone.availableResources().get("AvailableResource")) { + for (SupportedResource supportedResource : availableResource.supportedResources() + .get("SupportedResource")) { + if (SupportedResource.Status.AVAILABLE == supportedResource.status()) { + instanceTypeIds.add(supportedResource.value()); + } + } + } + } + return instanceTypeIds; + } + + @Override + public Iterable<ImageInRegion> listImages() { + final ImmutableList.Builder<ImageInRegion> imagesInRegion = ImmutableList.builder(); + + for (final String regionId : getAvailableLocationNames()) { + imagesInRegion.addAll(api.imageApi().list(regionId).concat() + .transform(new Function<Image, ImageInRegion>() { + @Override + public ImageInRegion apply(Image image) { + return ImageInRegion.create(regionId, image); + } + }) + ); + } + return imagesInRegion.build(); + } + + @Override + public ImageInRegion getImage(final String id) { + RegionAndId regionAndId = fromSlashEncoded(id); + Image image = api.imageApi().list(regionAndId.regionId(), ListImagesOptions.Builder.imageIds(regionAndId.id())) + .firstMatch(Predicates.<Image>notNull()) + .orNull(); + if (image == null) return null; + return ImageInRegion.create(regionAndId.regionId(), image); + } + + @Override + public Iterable<Region> listLocations() { + return FluentIterable.from(api.regionAndZoneApi().describeRegions()).filter(new Predicate<Region>() { + @Override + public boolean apply(Region region) { + return regionIds.get().isEmpty() ? true : regionIds.get().contains(region.id()); + } + }).toList(); + } + + @Override + public Instance getNode(final String id) { + RegionAndId regionAndId = fromSlashEncoded(id); + return api.instanceApi().list(regionAndId.regionId(), + ListInstancesOptions.Builder.instanceIds(regionAndId.id())) + .firstMatch(Predicates.<Instance>notNull()) + .orNull(); + } + + @Override + public void destroyNode(String id) { + checkState(cleanupResources.cleanupNode(RegionAndId.fromSlashEncoded(id)), "server(%s) and its resources still there after deleting!?", id); + } + + @Override + public void rebootNode(String id) { + api.instanceApi().reboot(id); + } + + @Override + public void resumeNode(String id) { + api.instanceApi().powerOn(id); + } + + @Override + public void suspendNode(String id) { + api.instanceApi().powerOff(id); + } + + @Override + public Iterable<Instance> listNodes() { + final ImmutableList.Builder<Instance> instances = ImmutableList.builder(); + for (String regionId : getAvailableLocationNames()) { + instances.addAll(api.instanceApi().list(regionId).concat()); + } + return instances.build(); + } + + @Override + public Iterable<Instance> listNodesByIds(final Iterable<String> ids) { + + final ImmutableList.Builder<Instance> instancesBuilder = ImmutableList.builder(); + for (String regionId : getAvailableLocationNames()) { + instancesBuilder.addAll(api.instanceApi().list(regionId, ListInstancesOptions.Builder.instanceIds(Iterables.toArray(ids, String.class)))); + } + return instancesBuilder.build(); + } + + private List<String> getAvailableLocationNames() { + return newArrayList( + Iterables.transform(listLocations(), new Function<Region, String>() { + @Override + public String apply(Region location) { + return location.id(); + } + })); + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/config/ECSServiceContextModule.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/config/ECSServiceContextModule.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/config/ECSServiceContextModule.java new file mode 100644 index 0000000..ace98e9 --- /dev/null +++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/config/ECSServiceContextModule.java @@ -0,0 +1,160 @@ +/* + * 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.config; + +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; +import org.jclouds.aliyun.ecs.ECSComputeServiceApi; +import org.jclouds.aliyun.ecs.compute.ECSComputeService; +import org.jclouds.aliyun.ecs.compute.ECSComputeServiceAdapter; +import org.jclouds.aliyun.ecs.compute.functions.ImageInRegionToImage; +import org.jclouds.aliyun.ecs.compute.functions.InstanceStatusToStatus; +import org.jclouds.aliyun.ecs.compute.functions.InstanceToNodeMetadata; +import org.jclouds.aliyun.ecs.compute.functions.InstanceTypeToHardware; +import org.jclouds.aliyun.ecs.compute.functions.RegionToLocation; +import org.jclouds.aliyun.ecs.compute.strategy.CreateResourcesThenCreateNodes; +import org.jclouds.aliyun.ecs.domain.Instance; +import org.jclouds.aliyun.ecs.domain.InstanceStatus; +import org.jclouds.aliyun.ecs.domain.InstanceType; +import org.jclouds.aliyun.ecs.domain.Region; +import org.jclouds.aliyun.ecs.domain.regionscoped.ImageInRegion; +import org.jclouds.aliyun.ecs.domain.regionscoped.RegionAndId; +import org.jclouds.aliyun.ecs.compute.options.ECSServiceTemplateOptions; +import org.jclouds.aliyun.ecs.predicates.InstanceStatusPredicate; +import org.jclouds.compute.ComputeService; +import org.jclouds.compute.ComputeServiceAdapter; +import org.jclouds.compute.config.ComputeServiceAdapterContextModule; +import org.jclouds.compute.domain.Hardware; +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.functions.NodeAndTemplateOptionsToStatement; +import org.jclouds.compute.functions.NodeAndTemplateOptionsToStatementWithoutPublicKey; +import org.jclouds.compute.options.TemplateOptions; +import org.jclouds.compute.reference.ComputeServiceConstants; +import org.jclouds.compute.strategy.CreateNodesInGroupThenAddToSet; +import org.jclouds.domain.Location; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_TERMINATED; +import static org.jclouds.util.Predicates2.retry; + +public class ECSServiceContextModule extends ComputeServiceAdapterContextModule<Instance, InstanceType, ImageInRegion, Region> { + + @Override + protected void configure() { + super.configure(); + + bind(new TypeLiteral<ComputeServiceAdapter<Instance, InstanceType, ImageInRegion, Region>>() { + }).to(ECSComputeServiceAdapter.class); + bind(ComputeService.class).to(ECSComputeService.class); + + bind(new TypeLiteral<Function<Instance, NodeMetadata>>() { + }).to(InstanceToNodeMetadata.class); + bind(new TypeLiteral<Function<InstanceType, Hardware>>() { + }).to(InstanceTypeToHardware.class); + bind(new TypeLiteral<Function<ImageInRegion, org.jclouds.compute.domain.Image>>() { + }).to(ImageInRegionToImage.class); + bind(new TypeLiteral<Function<Region, Location>>() { + }).to(RegionToLocation.class); + bind(new TypeLiteral<Function<Instance.Status, NodeMetadata.Status>>() { + }).to(InstanceStatusToStatus.class); + install(new LocationsFromComputeServiceAdapterModule<Instance, InstanceType, ImageInRegion, Region>() { + }); + bind(TemplateOptions.class).to(ECSServiceTemplateOptions.class); + bind(CreateNodesInGroupThenAddToSet.class).to(CreateResourcesThenCreateNodes.class); + bind(NodeAndTemplateOptionsToStatement.class).to(NodeAndTemplateOptionsToStatementWithoutPublicKey.class); + } + + @Provides + @Named(TIMEOUT_NODE_RUNNING) + protected Predicate<String> provideInstanceRunningPredicate(final ECSComputeServiceApi api, + ComputeServiceConstants.Timeouts timeouts, ComputeServiceConstants.PollPeriod pollPeriod) { + return retry(new InstanceInStatusPredicate(api, InstanceStatus.Status.RUNNING), timeouts.nodeRunning, + pollPeriod.pollInitialPeriod, pollPeriod.pollMaxPeriod); + } + + @Provides + @Named(TIMEOUT_NODE_SUSPENDED) + protected Predicate<String> provideInstanceSuspendedPredicate(final ECSComputeServiceApi api, + ComputeServiceConstants.Timeouts timeouts, ComputeServiceConstants.PollPeriod pollPeriod) { + return retry(new InstanceInStatusPredicate(api, InstanceStatus.Status.STOPPED), timeouts.nodeSuspended, + pollPeriod.pollInitialPeriod, pollPeriod.pollMaxPeriod); + } + + @Provides + @Named(TIMEOUT_NODE_TERMINATED) + protected Predicate<String> provideInstanceTerminatedPredicate(final ECSComputeServiceApi api, + ComputeServiceConstants.Timeouts timeouts, ComputeServiceConstants.PollPeriod pollPeriod) { + return retry(new InstanceTerminatedPredicate(api), timeouts.nodeTerminated, pollPeriod.pollInitialPeriod, + pollPeriod.pollMaxPeriod); + } + + @VisibleForTesting + static class InstanceInStatusPredicate implements Predicate<String> { + + private final ECSComputeServiceApi api; + private final InstanceStatus.Status desiredStatus; + + public InstanceInStatusPredicate(ECSComputeServiceApi api, InstanceStatus.Status desiredStatus) { + this.api = checkNotNull(api, "api must not be null"); + this.desiredStatus = checkNotNull(desiredStatus, "instance status must not be null"); + } + + @Override + public boolean apply(String id) { + checkNotNull(id, "id"); + RegionAndId regionAndId = RegionAndId.fromSlashEncoded(id); + String regionId = regionAndId.regionId(); + String instanceId = regionAndId.id(); + InstanceStatus instanceStatus = api.instanceApi().listInstanceStatus(regionId) + .concat() + .firstMatch(new InstanceStatusPredicate(instanceId)) + .orNull(); + return instanceStatus != null && desiredStatus == instanceStatus.status(); + } + } + + @VisibleForTesting + static class InstanceTerminatedPredicate implements Predicate<String> { + + private final ECSComputeServiceApi api; + + public InstanceTerminatedPredicate(ECSComputeServiceApi api) { + this.api = checkNotNull(api, "api must not be null"); + } + + @Override + public boolean apply(String id) { + checkNotNull(id, "id"); + RegionAndId regionAndId = RegionAndId.fromSlashEncoded(id); + String regionId = regionAndId.regionId(); + final String instanceId = regionAndId.id(); + InstanceStatus instanceStatus = api.instanceApi().listInstanceStatus(regionId) + .concat() + .firstMatch(new InstanceStatusPredicate(instanceId)) + .orNull(); + return instanceStatus == null; + } + + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/ImageInRegionToImage.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/ImageInRegionToImage.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/ImageInRegionToImage.java new file mode 100644 index 0000000..72c45cf --- /dev/null +++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/ImageInRegionToImage.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.compute.functions; + +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.ImmutableMap; +import com.google.inject.Inject; +import org.jclouds.aliyun.ecs.compute.functions.internal.OperatingSystems; +import org.jclouds.aliyun.ecs.domain.regionscoped.ImageInRegion; +import org.jclouds.aliyun.ecs.domain.regionscoped.RegionAndId; +import org.jclouds.collect.Memoized; +import org.jclouds.compute.domain.Image; +import org.jclouds.compute.domain.ImageBuilder; +import org.jclouds.compute.domain.OsFamily; +import org.jclouds.domain.Location; +import org.jclouds.location.predicates.LocationPredicates; + +import java.util.Map; +import java.util.Set; + +import static com.google.common.collect.FluentIterable.from; +import static com.google.common.collect.Iterables.tryFind; +import static java.util.Arrays.asList; +import static org.jclouds.compute.domain.OperatingSystem.builder; + +public class ImageInRegionToImage implements Function<ImageInRegion, Image> { + + private final Supplier<Set<? extends Location>> locations; + + private static final Map<String, OsFamily> OTHER_OS_MAP = ImmutableMap.<String, OsFamily>builder() + .put("Aliyun", OsFamily.LINUX).build(); + + private static Optional<OsFamily> findInStandardFamilies(final String platform) { + return tryFind(asList(OsFamily.values()), new Predicate<OsFamily>() { + @Override + public boolean apply(OsFamily input) { + return platform.toUpperCase().startsWith(input.name()); + } + }); + } + + private static Optional<OsFamily> findInOtherOSMap(final String label) { + return tryFind(OTHER_OS_MAP.keySet(), new Predicate<String>() { + @Override + public boolean apply(String input) { + return label.contains(input); + } + }).transform(new Function<String, OsFamily>() { + @Override + public OsFamily apply(String input) { + return OTHER_OS_MAP.get(input); + } + }); + } + + @Inject + ImageInRegionToImage(@Memoized Supplier<Set<? extends Location>> locations) { + this.locations = locations; + } + + @Override + public Image apply(ImageInRegion from) { + ImageBuilder builder = new ImageBuilder(); + builder.id(RegionAndId.slashEncodeRegionAndId(from.regionId(), from.image().id())); + builder.providerId(from.image().id()); + builder.name(from.image().name()); + builder.description(from.image().description()); + builder.status(from.image().status() == org.jclouds.aliyun.ecs.domain.Image.Status.AVAILABLE ? + Image.Status.AVAILABLE : Image.Status.PENDING); + + OsFamily family = findInStandardFamilies(from.image().platform()) + .or(findInOtherOSMap(from.image().platform())) + .or(OsFamily.UNRECOGNIZED); + + String osVersion = OperatingSystems.version().apply(from.image()); + + builder.operatingSystem( + builder().name(from.image().osName()).family(family) + .description(from.image().description()) + .version(osVersion) + .is64Bit("x86_64".equals(from.image().architecture()) ? true : false).build()); + + builder.location(from(locations.get()).firstMatch(LocationPredicates.idEquals(from.regionId())).orNull()); + return builder.build(); + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceStatusToStatus.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceStatusToStatus.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceStatusToStatus.java new file mode 100644 index 0000000..8fcaf18 --- /dev/null +++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceStatusToStatus.java @@ -0,0 +1,44 @@ +/* + * 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.functions; + +import com.google.common.base.Function; +import com.google.common.base.Functions; +import com.google.common.collect.ImmutableMap; +import org.jclouds.aliyun.ecs.domain.Instance; +import org.jclouds.compute.domain.NodeMetadata; + +import javax.inject.Singleton; + +/** + * Transforms an {@link Instance.Status} to the jclouds portable model. + */ +@Singleton +public class InstanceStatusToStatus implements Function<Instance.Status, NodeMetadata.Status> { + + private static final Function<Instance.Status, NodeMetadata.Status> toPortableStatus = Functions.forMap( + ImmutableMap.<Instance.Status, NodeMetadata.Status>builder() + .put(Instance.Status.STARTING, NodeMetadata.Status.PENDING) + .put(Instance.Status.STOPPING, NodeMetadata.Status.PENDING) + .put(Instance.Status.STOPPED, NodeMetadata.Status.SUSPENDED) + .put(Instance.Status.RUNNING, NodeMetadata.Status.RUNNING).build(), NodeMetadata.Status.UNRECOGNIZED); + + @Override + public NodeMetadata.Status apply(Instance.Status input) { + return toPortableStatus.apply(input); + } +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceToNodeMetadata.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceToNodeMetadata.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceToNodeMetadata.java new file mode 100644 index 0000000..1ead7ce --- /dev/null +++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceToNodeMetadata.java @@ -0,0 +1,126 @@ +/* + * 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.functions; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableMap; +import org.jclouds.aliyun.ecs.domain.Instance; +import org.jclouds.aliyun.ecs.domain.Tag; +import org.jclouds.aliyun.ecs.domain.regionscoped.RegionAndId; +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.NodeMetadataBuilder; +import org.jclouds.compute.functions.GroupNamingConvention; +import org.jclouds.compute.reference.ComputeServiceConstants; +import org.jclouds.domain.Location; +import org.jclouds.location.predicates.LocationPredicates; +import org.jclouds.logging.Logger; + +import javax.annotation.Resource; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import java.util.Map; +import java.util.Set; + +import static com.google.common.collect.FluentIterable.from; +import static org.jclouds.compute.util.ComputeServiceUtils.addMetadataAndParseTagsFromValuesOfEmptyString; + +/** + * Transforms an {@link Instance} to the jclouds portable model. + */ +@Singleton +public class InstanceToNodeMetadata implements Function<Instance, NodeMetadata> { + + 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<Instance.Status, NodeMetadata.Status> toPortableStatus; + private final GroupNamingConvention groupNamingConvention; + + @Resource @Named(ComputeServiceConstants.COMPUTE_LOGGER) + protected Logger logger = Logger.NULL; + + @Inject + InstanceToNodeMetadata(Supplier<Map<String, ? extends Image>> images, + Supplier<Map<String, ? extends Hardware>> hardwares, + @Memoized Supplier<Set<? extends Location>> locations, + Function<Instance.Status, NodeMetadata.Status> toPortableStatus, + GroupNamingConvention.Factory groupNamingConvention) { + this.images = images; + this.hardwares = hardwares; + this.locations = locations; + this.toPortableStatus = toPortableStatus; + this.groupNamingConvention = groupNamingConvention.createWithoutPrefix(); + } + + @Override + public NodeMetadata apply(Instance from) { + NodeMetadataBuilder builder = new NodeMetadataBuilder(); + + Optional<? extends Image> image = findImage(from.imageId()); + if (image.isPresent()) { + builder.imageId(image.get().getId()); + builder.operatingSystem(image.get().getOperatingSystem()); + } else { + logger.info(">> image with id %s for instance %s was not found. " + + "This might be because the image that was used to create the instance has a new id.", + from.instanceType(), from.id()); + } + Optional<? extends Hardware> hardware = findHardware(from.instanceType()); + if (hardware.isPresent()) { + builder.hardware(hardware.get()); + } else { + logger.info(">> hardware with id %s for instance %s was not found. " + + "This might be because the image that was used to create the instance has a new id.", + from.instanceType(), from.id()); + } + + builder.id(RegionAndId.slashEncodeRegionAndId(from.regionId(), from.id())); + builder.providerId(from.id()); + builder.name(from.name()); + builder.hostname(String.format("%s", from.hostname())); + builder.group(groupNamingConvention.extractGroup(from.name())); + builder.status(toPortableStatus.apply(from.status())); + builder.privateAddresses(from.innerIpAddress().entrySet().iterator().next().getValue()); + builder.publicAddresses(from.publicIpAddress().entrySet().iterator().next().getValue()); + builder.location(from(locations.get()).firstMatch(LocationPredicates.idEquals(from.regionId())).orNull()); + if (from.tags() != null && !from.tags().isEmpty()) { + ImmutableMap.Builder tagsBuilder = new ImmutableMap.Builder(); + for (Tag tag : from.tags().entrySet().iterator().next().getValue()) { + tagsBuilder.put(tag.key(), tag.value()); + } + addMetadataAndParseTagsFromValuesOfEmptyString(builder, tagsBuilder.build()); + } + + NodeMetadata nodeMetadata = builder.build(); + return nodeMetadata; + } + + private Optional<? extends Image> findImage(String imageId) { + return Optional.fromNullable(images.get().get(imageId)); + } + + private Optional<? extends Hardware> findHardware(String instanceType) { + return Optional.fromNullable(hardwares.get().get(instanceType)); + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceTypeToHardware.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceTypeToHardware.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceTypeToHardware.java new file mode 100644 index 0000000..8197979 --- /dev/null +++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/InstanceTypeToHardware.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.functions; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; +import org.jclouds.aliyun.ecs.domain.InstanceType; +import org.jclouds.compute.domain.Hardware; +import org.jclouds.compute.domain.HardwareBuilder; +import org.jclouds.compute.domain.Processor; + +import javax.inject.Singleton; + +@Singleton +public class InstanceTypeToHardware implements Function<InstanceType, Hardware> { + + private static final int GB_TO_MB_MULTIPLIER = 1024; + + @Override + public Hardware apply(InstanceType input) { + HardwareBuilder builder = new HardwareBuilder() + .ids(input.id()) + .name(input.id()) + .hypervisor("none") + .processors(getProcessors(input.cpuCoreCount())) + .ram(input.memorySize().intValue() * GB_TO_MB_MULTIPLIER); + return builder.build(); + } + + private Iterable<Processor> getProcessors(Integer cpuCoreCount) { + // No cpu speed from API, so assume more cores == faster + return ImmutableList.of(new Processor(cpuCoreCount, cpuCoreCount)); + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/RegionToLocation.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/RegionToLocation.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/RegionToLocation.java new file mode 100644 index 0000000..2bca519 --- /dev/null +++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/RegionToLocation.java @@ -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.aliyun.ecs.compute.functions; + +import com.google.common.base.Function; +import org.jclouds.aliyun.ecs.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 javax.inject.Inject; +import javax.inject.Singleton; + +import static com.google.common.collect.Iterables.getOnlyElement; + +/** + * Transforms an {@link Region} to the jclouds portable model. + */ +@Singleton +public class RegionToLocation implements Function<Region, Location> { + + private final JustProvider justProvider; + + // allow us to lazy discover the provider of a resource + @Inject + RegionToLocation(JustProvider justProvider) { + this.justProvider = justProvider; + } + + @Override + public Location apply(final Region region) { + final LocationBuilder builder = new LocationBuilder(); + builder.id(region.id()); + builder.description(region.localName()); + builder.parent(getOnlyElement(justProvider.get())); + builder.scope(LocationScope.REGION); + return builder.build(); + } +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/internal/OperatingSystems.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/internal/OperatingSystems.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/internal/OperatingSystems.java new file mode 100644 index 0000000..93681b2 --- /dev/null +++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/functions/internal/OperatingSystems.java @@ -0,0 +1,51 @@ +/* + * 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.functions.internal; + +import com.google.common.base.Function; +import com.google.common.base.Splitter; +import com.google.common.collect.Iterables; +import org.jclouds.aliyun.ecs.domain.Image; +import org.jclouds.compute.domain.OsFamily; + +public class OperatingSystems { + + public static Function<Image, String> version() { + return new Function<Image, String>() { + @Override + public String apply(final Image image) { + return parseVersion(image); + } + }; + } + + private static String parseVersion(Image image) { + String sequence = image.osName().trim().replaceAll("\\s+", " "); + int offset = 2; + if (isWindows(image)) { + sequence = image.platform(); + offset = 1; + } + Iterable<String> splitted = Splitter.on(" ").split(sequence); + return Iterables.get(splitted, Iterables.size(splitted) - offset); + } + + public static boolean isWindows(Image image) { + return image.platform().toUpperCase().startsWith(OsFamily.WINDOWS.name()); + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/options/ECSServiceTemplateOptions.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/options/ECSServiceTemplateOptions.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/options/ECSServiceTemplateOptions.java new file mode 100644 index 0000000..c378cbd --- /dev/null +++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/options/ECSServiceTemplateOptions.java @@ -0,0 +1,180 @@ +/* + * 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.options; + +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; +import org.jclouds.compute.options.TemplateOptions; + +import static com.google.common.base.Objects.equal; + +/** + * Custom options for the Alibaba Elastic Compute Service API. + */ +public class ECSServiceTemplateOptions extends TemplateOptions implements Cloneable { + + private String keyPairName = ""; + private String vSwitchId = ""; + private String internetChargeType = "PayByTraffic"; + private String instanceChargeType = "PostPaid"; + private int internetMaxBandwidthOut = 5; + + public ECSServiceTemplateOptions keyPairName(String keyPairName) { + this.keyPairName = keyPairName; + return this; + } + + public ECSServiceTemplateOptions vSwitchId(String vSwitchId) { + this.vSwitchId = vSwitchId; + return this; + } + + public ECSServiceTemplateOptions internetChargeType(String internetChargeType) { + this.internetChargeType = internetChargeType; + return this; + } + + public ECSServiceTemplateOptions instanceChargeType(String instanceChargeType) { + this.instanceChargeType = instanceChargeType; + return this; + } + + public ECSServiceTemplateOptions internetMaxBandwidthOut(int internetMaxBandwidthOut) { + this.internetMaxBandwidthOut = internetMaxBandwidthOut; + return this; + } + + public String getKeyPairName() { + return keyPairName; + } + + public String getVSwitchId() { + return vSwitchId; + } + + public String getInternetChargeType() { + return internetChargeType; + } + + public String getInstanceChargeType() { + return instanceChargeType; + } + + public int getInternetMaxBandwidthOut() { + return internetMaxBandwidthOut; + } + + @Override + public ECSServiceTemplateOptions clone() { + ECSServiceTemplateOptions options = new ECSServiceTemplateOptions(); + copyTo(options); + return options; + } + + @Override + public void copyTo(TemplateOptions to) { + super.copyTo(to); + if (to instanceof ECSServiceTemplateOptions) { + ECSServiceTemplateOptions eTo = ECSServiceTemplateOptions.class.cast(to); + eTo.keyPairName(keyPairName); + eTo.vSwitchId(vSwitchId); + eTo.internetChargeType(internetChargeType); + eTo.instanceChargeType(instanceChargeType); + eTo.internetMaxBandwidthOut(internetMaxBandwidthOut); + } + } + + @Override + public int hashCode() { + return Objects.hashCode(super.hashCode(), keyPairName, vSwitchId, internetChargeType, instanceChargeType, internetMaxBandwidthOut); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + ECSServiceTemplateOptions other = (ECSServiceTemplateOptions) obj; + return super.equals(other) && + equal(this.keyPairName, other.keyPairName) && + equal(this.vSwitchId, other.vSwitchId) && + equal(this.internetChargeType, other.internetChargeType) && + equal(this.instanceChargeType, other.instanceChargeType) && + equal(this.internetMaxBandwidthOut, other.internetMaxBandwidthOut); + } + + @Override + public MoreObjects.ToStringHelper string() { + MoreObjects.ToStringHelper toString = super.string().omitNullValues(); + toString.add("keyPairName", keyPairName); + toString.add("vSwitchId", vSwitchId); + toString.add("internetChargeType", internetChargeType); + toString.add("instanceChargeType", instanceChargeType); + toString.add("internetMaxBandwidthOut", internetMaxBandwidthOut); + return toString; + } + + public static class Builder { + + /** + * @see ECSServiceTemplateOptions#keyPairName + */ + public static ECSServiceTemplateOptions keyPairName(String keyPairName) { + ECSServiceTemplateOptions options = new ECSServiceTemplateOptions(); + return options.keyPairName(keyPairName); + } + + /** + * @see ECSServiceTemplateOptions#vSwitchId + */ + public static ECSServiceTemplateOptions vSwitchId(String vSwitchId) { + ECSServiceTemplateOptions options = new ECSServiceTemplateOptions(); + return options.vSwitchId(vSwitchId); + } + + /** + * @see ECSServiceTemplateOptions#internetChargeType + */ + public static ECSServiceTemplateOptions internetChargeType(String internetChargeType) { + ECSServiceTemplateOptions options = new ECSServiceTemplateOptions(); + return options.internetChargeType(internetChargeType); + } + + /** + * @see ECSServiceTemplateOptions#instanceChargeType + */ + public static ECSServiceTemplateOptions instanceChargeType(String instanceChargeType) { + ECSServiceTemplateOptions options = new ECSServiceTemplateOptions(); + return options.instanceChargeType(instanceChargeType); + } + + /** + * @see ECSServiceTemplateOptions#internetMaxBandwidthOut + */ + public static ECSServiceTemplateOptions internetMaxBandwidthOut(int internetMaxBandwidthOut) { + ECSServiceTemplateOptions options = new ECSServiceTemplateOptions(); + return options.internetMaxBandwidthOut(internetMaxBandwidthOut); + } + + } +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/strategy/CleanupResources.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/strategy/CleanupResources.java b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/strategy/CleanupResources.java new file mode 100644 index 0000000..8fb5730 --- /dev/null +++ b/aliyun-ecs/src/main/java/org/jclouds/aliyun/ecs/compute/strategy/CleanupResources.java @@ -0,0 +1,112 @@ +/* + * 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.strategy; + +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import org.jclouds.aliyun.ecs.ECSComputeServiceApi; +import org.jclouds.aliyun.ecs.domain.InstanceStatus; +import org.jclouds.aliyun.ecs.domain.SecurityGroup; +import org.jclouds.aliyun.ecs.domain.Tag; +import org.jclouds.aliyun.ecs.domain.regionscoped.RegionAndId; +import org.jclouds.aliyun.ecs.predicates.InstanceStatusPredicate; +import org.jclouds.compute.reference.ComputeServiceConstants; +import org.jclouds.logging.Logger; + +import javax.annotation.Nullable; +import javax.annotation.Resource; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import java.util.List; + +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_TERMINATED; + +/** + * This utility takes care of cleaning up all the resources created to deploy the node + * + * Specifically, it tries to delete the security group created for the group of nodes. + * In case a VPC_PREFIX and a vSwitch were created for the node, it tries to remove them + */ +@Singleton +public class CleanupResources { + + @Resource + @Named(ComputeServiceConstants.COMPUTE_LOGGER) + protected Logger logger = Logger.NULL; + + private final ECSComputeServiceApi api; + private final Predicate<String> instanceSuspendedPredicate; + private final Predicate<String> instanceTerminatedPredicate; + + @Inject + public CleanupResources(ECSComputeServiceApi api, + @Named(TIMEOUT_NODE_SUSPENDED) Predicate<String> instanceSuspendedPredicate, + @Named(TIMEOUT_NODE_TERMINATED) Predicate<String> instanceTerminatedPredicate + ) { + this.api = api; + this.instanceSuspendedPredicate = instanceSuspendedPredicate; + this.instanceTerminatedPredicate = instanceTerminatedPredicate; + } + + /** + * @param regionAndId + * @return whether the node and its resources have been deleted + */ + public boolean cleanupNode(final RegionAndId regionAndId) { + String instanceId = regionAndId.id(); + InstanceStatus instanceStatus = Iterables.tryFind(api.instanceApi().listInstanceStatus(regionAndId.regionId()).concat(), + new InstanceStatusPredicate(instanceId)).orNull(); + if (instanceStatus == null) return true; + if (InstanceStatus.Status.STOPPED != instanceStatus.status()) { + logger.debug(">> powering off %s ...", RegionAndId.slashEncodeRegionAndId(regionAndId)); + api.instanceApi().powerOff(instanceId); + instanceSuspendedPredicate.apply(RegionAndId.slashEncodeRegionAndId(regionAndId)); + } + logger.debug(">> destroying %s ...", RegionAndId.slashEncodeRegionAndId(regionAndId)); + api.instanceApi().delete(instanceId); + return instanceTerminatedPredicate.apply(RegionAndId.slashEncodeRegionAndId(regionAndId)); + } + + public List<SecurityGroup> findOrphanedSecurityGroups(final String regionId, final String group) { + return api.securityGroupApi().list(regionId).concat().filter(new Predicate<SecurityGroup>() { + @Override + public boolean apply(@Nullable SecurityGroup input) { + List<Tag> actual = input.tags().entrySet().iterator().next().getValue(); + List<Tag> expected = ImmutableList.of( + Tag.create(Tag.DEFAULT_OWNER_KEY, Tag.DEFAULT_OWNER_VALUE), Tag.create(Tag.GROUP, group) + ); + return actual.containsAll(expected) && expected.containsAll(actual); + } + }).toList(); + } + + public boolean cleanupSecurityGroupIfOrphaned(final String regionId, String securityGroupId) { + return api.securityGroupApi().delete(regionId, securityGroupId) != null; + } + + public boolean cleanupVSwitchIfOrphaned(final String regionId, String vSwitchId) { + return api.vSwitchApi().delete(regionId, vSwitchId) != null; + } + + public boolean cleanupVPCIfOrphaned(final String regionId, String vpcId) { + return api.vpcApi().delete(regionId, vpcId) != null; + } + +}
