http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/functions/InstanceToNodeMetadataTest.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/functions/InstanceToNodeMetadataTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/functions/InstanceToNodeMetadataTest.java new file mode 100644 index 0000000..2cefe83 --- /dev/null +++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/functions/InstanceToNodeMetadataTest.java @@ -0,0 +1,249 @@ +/* + * 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.Supplier; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.inject.Guice; +import org.jclouds.aliyun.ecs.domain.DedicatedHostAttribute; +import org.jclouds.aliyun.ecs.domain.EipAddress; +import org.jclouds.aliyun.ecs.domain.Instance; +import org.jclouds.aliyun.ecs.domain.NetworkInterface; +import org.jclouds.aliyun.ecs.domain.Tag; +import org.jclouds.aliyun.ecs.domain.VpcAttributes; +import org.jclouds.aliyun.ecs.domain.internal.Regions; +import org.jclouds.aliyun.ecs.domain.regionscoped.RegionAndId; +import org.jclouds.compute.domain.Hardware; +import org.jclouds.compute.domain.HardwareBuilder; +import org.jclouds.compute.domain.Image; +import org.jclouds.compute.domain.ImageBuilder; +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.domain.OperatingSystem; +import org.jclouds.compute.domain.OsFamily; +import org.jclouds.compute.domain.Processor; +import org.jclouds.compute.functions.GroupNamingConvention; +import org.jclouds.date.internal.SimpleDateFormatDateService; +import org.jclouds.domain.Location; +import org.jclouds.domain.LocationBuilder; +import org.jclouds.domain.LocationScope; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + +@Test(groups = "unit", testName = "InstanceToNodeMetadataTest") +public class InstanceToNodeMetadataTest { + + private InstanceToNodeMetadata instanceToNodeMetadata; + private Image image; + private Hardware hardware; + private Location location; + private Instance instance; + private OperatingSystem os; + private Map<String, List<Tag>> tags; + + private String imageId = "centos_6_09_64_20G_alibase_20180326.vhd"; + private String hardwareId = "ecs.t1.xsmall"; + private String regionId = Regions.EU_CENTRAL_1.getName(); + + @BeforeMethod + public void setUp() { + + location = new LocationBuilder().id(regionId) + .description(Regions.EU_CENTRAL_1.getDescription()) + .scope(LocationScope.PROVIDER) + .build(); + Supplier<Set<? extends Location>> locations = new Supplier<Set<? extends Location>>() { + @Override + public Set<? extends Location> get() { + return ImmutableSet.of(location); + } + }; + + GroupNamingConvention.Factory namingConvention = Guice.createInjector().getInstance(GroupNamingConvention.Factory.class); + + hardware = new HardwareBuilder() + .ids(hardwareId) + .name(hardwareId) + .ram(1024) + .processor(new Processor(1, 1d)) + .location(location) + .build(); + + os = OperatingSystem.builder() + .description("CentOS 6.9 64bit") + .family(OsFamily.CENTOS) + .version("6.9") + .is64Bit(true) + .build(); + + image = new ImageBuilder() + .id(RegionAndId.slashEncodeRegionAndId(regionId, imageId)) + .providerId(imageId) + .name("CentOS 6.9 64ä½") + .description("") + .operatingSystem(os) + .status(Image.Status.AVAILABLE) + .build(); + + tags = ImmutableMap.<String, List<Tag>>of("Tag", ImmutableList.of(Tag.create("hello", ""))); + + instance = Instance.builder() + .id("serverId") + .name("instanceName") + .regionId(regionId) + .imageId(imageId) + .instanceType(hardwareId) + .instanceTypeFamily("linux") + .vlanId("vlanId") + .eipAddress(EipAddress.create("ipAddress", "allocationId", EipAddress.InternetChargeType.ECS_INSTANCE)) + .internetMaxBandwidthIn(1) + .zoneId("zoneId") + .internetChargeType(Instance.InternetChargeType.PAY_BY_TRAFFIC) + .spotStrategy("spotStrategy") + .stoppedMode("stoppedMode") + .serialNumber("serialNumber") + .ioOptimized(true) + .memory(1024) + .cpu(1) + .vpcAttributes(VpcAttributes.create("natIpAddress", ImmutableMap.<String, List<String>>of(), "vSwitchId", "vpcId")) + .internetMaxBandwidthOut(1) + .deviceAvailable(true) + .saleCycle("saleCycle") + .spotPriceLimit(1d) + .autoReleaseTime("") + .startTime(new SimpleDateFormatDateService().iso8601DateParse("2014-03-22T07:16:45.784120972Z")) + .description("desc") + .resourceGroupId("resourceGroupId") + .osType("osType") + .osName("osName") + .instanceNetworkType("instanceNetworkType") + .hostname("hostname") + .creationTime(new SimpleDateFormatDateService().iso8601DateParse("2014-03-22T05:16:45.784120972Z")) + .status(Instance.Status.RUNNING) + .clusterId("clusterId") + .recyclable(false) + .gpuSpec("") + .dedicatedHostAttribute(DedicatedHostAttribute.create("id", "name")) + .instanceChargeType("instanceChargeType") + .gpuAmount(1) + .expiredTime(new SimpleDateFormatDateService().iso8601DateParse("2014-03-22T09:16:45.784120972Z")) + .innerIpAddress(ImmutableMap.<String, List<String>>of("IpAddress", ImmutableList.of("192.168.0.1", "192.168.0.2"))) + .publicIpAddress(ImmutableMap.<String, List<String>>of("IpAddress", ImmutableList.of("47.254.152.220", "47.254.153.230"))) + .securityGroupIds(ImmutableMap.<String, List<String>>of()) + .networkInterfaces(ImmutableMap.<String, List<NetworkInterface>>of()) + .operationLocks(ImmutableMap.<String, List<String>>of()) + .tags(tags) + .build(); + + Supplier<Map<String, ? extends Image>> images = new Supplier<Map<String, ? extends Image>>() { + @Override + public Map<String, ? extends Image> get() { + return ImmutableMap.of(imageId, image); + } + }; + + Supplier<Map<String, ? extends Hardware>> hardwares = new Supplier<Map<String, ? extends Hardware>>() { + @Override + public Map<String, ? extends Hardware> get() { + return ImmutableMap.of(hardwareId, hardware); + } + }; + + instanceToNodeMetadata = new InstanceToNodeMetadata(images, hardwares, locations, + new InstanceStatusToStatus(), namingConvention); + } + + @Test + public void testInstanceToNodeMetadata() { + NodeMetadata node = instanceToNodeMetadata.apply(instance); + + List<String> privateIpAddresses = instance.innerIpAddress().entrySet().iterator().next().getValue(); + List<String> publicIpAddresses = instance.publicIpAddress().entrySet().iterator().next().getValue(); + + assertNotNull(node); + assertEquals(node.getProviderId(), instance.id()); + assertEquals(node.getName(), instance.name()); + assertEquals(node.getHostname(), instance.hostname()); + assertEquals(node.getGroup(), instance.name()); + assertEquals(node.getHardware(), hardware); + assertEquals(node.getImageId(), RegionAndId.slashEncodeRegionAndId(regionId, imageId)); + assertEquals(node.getOperatingSystem(), os); + assertEquals(node.getLocation(), location); + assertEquals(node.getImageId(), RegionAndId.slashEncodeRegionAndId(regionId, imageId)); + assertEquals(node.getStatus(), NodeMetadata.Status.RUNNING); + assertEquals(node.getPrivateAddresses(), privateIpAddresses); + assertEquals(node.getPublicAddresses(), publicIpAddresses); + assertEquals(node.getTags(), ImmutableSet.of("hello")); + } + + @Test + public void testInstanceWithInvalidHardwareToNodeMetadata() { + Instance instanceWithoutValidHardwareId = instance.toBuilder().instanceType("not.valid").build(); + NodeMetadata node = instanceToNodeMetadata.apply(instanceWithoutValidHardwareId); + + List<String> privateIpAddresses = instanceWithoutValidHardwareId.innerIpAddress().entrySet().iterator().next().getValue(); + List<String> publicIpAddresses = instanceWithoutValidHardwareId.publicIpAddress().entrySet().iterator().next().getValue(); + + assertNotNull(node); + assertEquals(node.getProviderId(), instanceWithoutValidHardwareId.id()); + assertEquals(node.getName(), instanceWithoutValidHardwareId.name()); + assertEquals(node.getHostname(), instanceWithoutValidHardwareId.hostname()); + assertEquals(node.getGroup(), instanceWithoutValidHardwareId.name()); + assertEquals(node.getHardware(), null); + assertEquals(node.getImageId(), RegionAndId.slashEncodeRegionAndId(regionId, imageId)); + assertEquals(node.getOperatingSystem(), os); + assertEquals(node.getLocation(), location); + assertEquals(node.getImageId(), RegionAndId.slashEncodeRegionAndId(regionId, imageId)); + assertEquals(node.getStatus(), NodeMetadata.Status.RUNNING); + assertEquals(node.getPrivateAddresses(), privateIpAddresses); + assertEquals(node.getPublicAddresses(), publicIpAddresses); + assertEquals(node.getTags(), ImmutableSet.of("hello")); + } + + @Test + public void testInstanceWithInvalidImageToNodeMetadata() { + Instance instanceWithoutValidHardwareId = instance.toBuilder().imageId("not.valid").build(); + NodeMetadata node = instanceToNodeMetadata.apply(instanceWithoutValidHardwareId); + + List<String> privateIpAddresses = instanceWithoutValidHardwareId.innerIpAddress().entrySet().iterator().next().getValue(); + List<String> publicIpAddresses = instanceWithoutValidHardwareId.publicIpAddress().entrySet().iterator().next().getValue(); + + assertNotNull(node); + assertEquals(node.getProviderId(), instanceWithoutValidHardwareId.id()); + assertEquals(node.getName(), instanceWithoutValidHardwareId.name()); + assertEquals(node.getHostname(), instanceWithoutValidHardwareId.hostname()); + assertEquals(node.getGroup(), instanceWithoutValidHardwareId.name()); + assertEquals(node.getHardware(), hardware); + assertEquals(node.getImageId(), null); + assertEquals(node.getOperatingSystem(), null); + assertEquals(node.getImageId(), null); + assertEquals(node.getLocation(), location); + assertEquals(node.getStatus(), NodeMetadata.Status.RUNNING); + assertEquals(node.getPrivateAddresses(), privateIpAddresses); + assertEquals(node.getPublicAddresses(), publicIpAddresses); + assertEquals(node.getTags(), ImmutableSet.of("hello")); + } + +}
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/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 index f7b9526..c18d2b6 100644 --- 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 @@ -16,19 +16,33 @@ */ package org.jclouds.aliyun.ecs.compute.internal; +import com.google.common.base.Predicate; import com.google.inject.Injector; +import com.google.inject.Key; import com.google.inject.Module; +import com.google.inject.TypeLiteral; +import com.google.inject.name.Names; import org.jclouds.aliyun.ecs.ECSComputeServiceApi; +import org.jclouds.aliyun.ecs.domain.internal.Regions; import org.jclouds.apis.BaseApiLiveTest; import org.jclouds.compute.config.ComputeServiceProperties; import java.util.Properties; import java.util.concurrent.TimeUnit; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED; + public class BaseECSComputeServiceApiLiveTest extends BaseApiLiveTest<ECSComputeServiceApi> { + protected static final String TEST_REGION = System.getProperty("test.alibaba-ecs.region", Regions.EU_CENTRAL_1.getName()); + protected static final String TEST_ZONE = System.getProperty("test.alibaba-ecs.zone", TEST_REGION + "a"); + + protected Predicate<String> instanceRunningPredicate; + protected Predicate<String> instanceSuspendedPredicate; + public BaseECSComputeServiceApiLiveTest() { - provider = "aliyun-ecs"; + provider = "alibaba-ecs"; } @Override @@ -43,6 +57,12 @@ public class BaseECSComputeServiceApiLiveTest extends BaseApiLiveTest<ECSCompute @Override protected ECSComputeServiceApi create(Properties props, Iterable<Module> modules) { Injector injector = newBuilder().modules(modules).overrides(props).buildInjector(); + + instanceRunningPredicate = injector.getInstance(Key.get(new TypeLiteral<Predicate<String>>() { + }, Names.named(TIMEOUT_NODE_RUNNING))); + instanceSuspendedPredicate = injector.getInstance(Key.get(new TypeLiteral<Predicate<String>>() { + }, Names.named(TIMEOUT_NODE_SUSPENDED))); + return injector.getInstance(ECSComputeServiceApi.class); } http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/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 index dd3938d..e25b08f 100644 --- 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 @@ -29,6 +29,7 @@ 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.aliyun.ecs.domain.internal.Regions; import org.jclouds.concurrent.config.ExecutorServiceModule; import org.jclouds.json.Json; import org.jclouds.rest.ApiContext; @@ -48,6 +49,8 @@ import static org.testng.Assert.assertTrue; public class BaseECSComputeServiceApiMockTest { private static final String DEFAULT_ENDPOINT = new ECSComputeServiceProviderMetadata().getEndpoint(); + protected static final String TEST_REGION = Regions.EU_CENTRAL_1.getName(); + protected static final String TEST_ZONE = TEST_REGION + "a"; private final Set<Module> modules = ImmutableSet.<Module>of(new ExecutorServiceModule(newDirectExecutorService())); protected MockWebServer server; @@ -59,7 +62,7 @@ public class BaseECSComputeServiceApiMockTest { public void start() throws IOException { server = new MockWebServer(); server.play(); - ctx = ContextBuilder.newBuilder("aliyun-ecs").credentials("user", "password").endpoint(url("")).modules(modules) + ctx = ContextBuilder.newBuilder("alibaba-ecs").credentials("user", "password").endpoint(url("")).modules(modules) .overrides(overrides()).build(); json = ctx.utils().injector().getInstance(Json.class); api = ctx.getApi(); http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/strategy/CreateResourcesThenCreateNodesTest.java ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/strategy/CreateResourcesThenCreateNodesTest.java b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/strategy/CreateResourcesThenCreateNodesTest.java new file mode 100644 index 0000000..4bff6d3 --- /dev/null +++ b/aliyun-ecs/src/test/java/org/jclouds/aliyun/ecs/compute/strategy/CreateResourcesThenCreateNodesTest.java @@ -0,0 +1,395 @@ +/* + * 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.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.ListeningExecutorService; +import org.easymock.EasyMock; +import org.jclouds.aliyun.ecs.ECSComputeServiceApi; +import org.jclouds.aliyun.ecs.compute.options.ECSServiceTemplateOptions; +import org.jclouds.aliyun.ecs.domain.IpProtocol; +import org.jclouds.aliyun.ecs.domain.KeyPairRequest; +import org.jclouds.aliyun.ecs.domain.Permission; +import org.jclouds.aliyun.ecs.domain.Request; +import org.jclouds.aliyun.ecs.domain.ResourceInfo; +import org.jclouds.aliyun.ecs.domain.ResourceType; +import org.jclouds.aliyun.ecs.domain.SecurityGroup; +import org.jclouds.aliyun.ecs.domain.SecurityGroupRequest; +import org.jclouds.aliyun.ecs.domain.Tag; +import org.jclouds.aliyun.ecs.domain.VPCRequest; +import org.jclouds.aliyun.ecs.domain.VSwitch; +import org.jclouds.aliyun.ecs.domain.VSwitchRequest; +import org.jclouds.aliyun.ecs.domain.Zone; +import org.jclouds.aliyun.ecs.domain.internal.PaginatedCollection; +import org.jclouds.aliyun.ecs.domain.internal.Regions; +import org.jclouds.aliyun.ecs.domain.options.CreateSecurityGroupOptions; +import org.jclouds.aliyun.ecs.domain.options.CreateVPCOptions; +import org.jclouds.aliyun.ecs.domain.options.CreateVSwitchOptions; +import org.jclouds.aliyun.ecs.domain.options.ListVSwitchesOptions; +import org.jclouds.aliyun.ecs.domain.options.TagOptions; +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.collect.IterableWithMarkers; +import org.jclouds.collect.PagedIterables; +import org.jclouds.compute.config.CustomizationResponse; +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.domain.Template; +import org.jclouds.compute.functions.GroupNamingConvention; +import org.jclouds.compute.reference.ComputeServiceConstants; +import org.jclouds.compute.strategy.CreateNodeWithGroupEncodedIntoName; +import org.jclouds.compute.strategy.CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap; +import org.jclouds.compute.strategy.ListNodesStrategy; +import org.jclouds.domain.Location; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import static org.easymock.EasyMock.anyString; +import static org.easymock.EasyMock.createNiceMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMockSupport.injectMocks; +import static org.jclouds.aliyun.ecs.compute.strategy.CreateResourcesThenCreateNodes.DEFAULT_CIDR_BLOCK; +import static org.jclouds.aliyun.ecs.compute.strategy.CreateResourcesThenCreateNodes.DEFAULT_DESCRIPTION_SUFFIX; +import static org.jclouds.aliyun.ecs.compute.strategy.CreateResourcesThenCreateNodes.VSWITCH_PREFIX; +import static org.testng.AssertJUnit.assertEquals; + +/** + * + * User can specify security group and vSwitch. + * 1. security group and vSwitch + * 2. only security group -> impossible to determine which vSwitch the user wants to use or create + * 3. only vswitch ID -> create a securitygroup in the same vpc + * 4. none of them -> create vpc, vswitch and securitygroup + * + * Case 1 is tested with testExecuteWithSecurityGroupsVSwitchId + * Case 2 testExecuteOnlySecurityGroup + * Case 3 is tested with testExecuteOnlyVSwitchId + * Case 4 is tested with testExecuteNoSecurityGroupsVSwitchId + */ +@Test(groups = "unit", testName = "CreateResourcesThenCreateNodesTest") +public class CreateResourcesThenCreateNodesTest { + + private CreateResourcesThenCreateNodes createResourcesThenCreateNodes; + private SecurityGroupApi securityGroupApi; + private VSwitchApi vSwitchApi; + private TagApi tagApi; + private SshKeyPairApi sshKeyPairApi; + private VPCApi vpcApi; + private RegionAndZoneApi regionAndZoneApi; + private ECSComputeServiceApi api; + private Template template; + private ECSServiceTemplateOptions templateOptions; + private CreateNodeWithGroupEncodedIntoName addNodeWithGroupStrategy; + private ListNodesStrategy listNodesStrategy; + private GroupNamingConvention.Factory factory; + private GroupNamingConvention namingConvention; + private ListeningExecutorService userExecutor; + private CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.Factory customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory; + private ComputeServiceConstants.Timeouts timeouts; + private Location location; + private String regionId; + private SecurityGroup securityGroup; + private Permission permission; + private VSwitch vswitch; + private Zone zone; + + @BeforeMethod + public void setUp() { + securityGroupApi = EasyMock.createMock(SecurityGroupApi.class); + vSwitchApi = EasyMock.createMock(VSwitchApi.class); + tagApi = EasyMock.createMock(TagApi.class); + sshKeyPairApi = EasyMock.createMock(SshKeyPairApi.class); + vpcApi = EasyMock.createMock(VPCApi.class); + regionAndZoneApi = EasyMock.createMock(RegionAndZoneApi.class); + api = EasyMock.createMock(ECSComputeServiceApi.class); + template = EasyMock.createNiceMock(Template.class); + addNodeWithGroupStrategy = EasyMock.createNiceMock(CreateNodeWithGroupEncodedIntoName.class); + listNodesStrategy = EasyMock.createNiceMock(ListNodesStrategy.class); + factory = EasyMock.createNiceMock(GroupNamingConvention.Factory.class); + namingConvention = EasyMock.createNiceMock(GroupNamingConvention.class); + + userExecutor = EasyMock.createNiceMock(ListeningExecutorService.class); + customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory = EasyMock + .createNiceMock(CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.Factory.class); + location = createNiceMock(Location.class); + templateOptions = new ECSServiceTemplateOptions(); + regionId = Regions.EU_CENTRAL_1.getName(); + + timeouts = new ComputeServiceConstants.Timeouts(); + + createResourcesThenCreateNodes = new CreateResourcesThenCreateNodes(addNodeWithGroupStrategy, + listNodesStrategy, factory, userExecutor, + customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory, api); + + permission = Permission.create( + "", + "", + "", + Permission.NicType.INTERNET, + "", + "", + "", + Permission.Direction.ALL, + "", + IpProtocol.ALL, + "", + Permission.Policy.ACCEPT, + new Date(), + "", + "", + "" + ); + + zone = Zone.create("id", + "localName", + ImmutableMap.<String, List<Object>>of(), + ImmutableMap.<String, List<String>>of(), + ImmutableMap.<String, List<String>>of(), + ImmutableMap.<String, List<ResourceInfo>>of(), + ImmutableMap.<String, List<String>>of(), + ImmutableMap.<String, List<String>>of(), + ImmutableMap.<String, List<String>>of() + ); + + injectMocks(api); + injectMocks(template); + injectMocks(securityGroupApi); + injectMocks(vSwitchApi); + injectMocks(tagApi); + injectMocks(sshKeyPairApi); + injectMocks(vpcApi); + injectMocks(regionAndZoneApi); + + expect(template.getLocation()).andReturn(location).anyTimes(); + expect(location.getId()).andReturn(regionId).anyTimes(); + + expect(api.securityGroupApi()).andReturn(securityGroupApi).anyTimes(); + expect(api.vSwitchApi()).andReturn(vSwitchApi).anyTimes(); + expect(api.tagApi()).andReturn(tagApi).anyTimes(); + expect(api.sshKeyPairApi()).andReturn(sshKeyPairApi).anyTimes(); + expect(api.vpcApi()).andReturn(vpcApi).anyTimes(); + expect(api.regionAndZoneApi()).andReturn(regionAndZoneApi).anyTimes(); + } + + + @Test + public void testExecuteWithSecurityGroupsVSwitchId() { + String vpcId = "vpc-1"; + String vSwitchId = "vs-1"; + String securityGroupId = "sg-1"; + securityGroup = createSecurityGroup(securityGroupId, vpcId); + vswitch = createVSwitch(vSwitchId, vpcId); + + templateOptions.vSwitchId(vSwitchId).securityGroups(securityGroupId); + + expect(template.getOptions()).andReturn(templateOptions).anyTimes(); + + expect(securityGroupApi.list(regionId)) + .andReturn(PagedIterables.onlyPage(IterableWithMarkers.from(Lists.newArrayList(securityGroup)))); + + expect(securityGroupApi.get(regionId, securityGroupId)) + .andReturn(Lists.newArrayList(permission)); + + // found VSwitch specified by user in VPC_PREFIX + expect(vSwitchApi.list(regionId, ListVSwitchesOptions.Builder.vSwitchId(vSwitchId).vpcId(vpcId))) + .andReturn(new PaginatedCollection(ImmutableMap.<String, Iterable<VSwitch>>of("VSwitch", Lists.<VSwitch> newArrayList(vswitch)), 1, 1, 1, regionId, "requestId")); + + expect(factory.create()).andReturn(namingConvention).anyTimes(); + expect(namingConvention.sharedNameForGroup(anyString())).andReturn("group").anyTimes(); + expect(namingConvention.uniqueNameForGroup(anyString())).andReturn("prefix").anyTimes(); + + expect(securityGroupApi.create(regionId, CreateSecurityGroupOptions.Builder.securityGroupName("group").vpcId(vpcId))) + .andReturn(new SecurityGroupRequest("requestId", securityGroupId)).anyTimes(); + expect(securityGroupApi.addInboundRule(regionId, securityGroupId, IpProtocol.TCP, "22/22", "0.0.0.0/0")) + .andReturn(new Request("requestId")).anyTimes(); + expect(tagApi.add(regionId, securityGroupId, ResourceType.SECURITYGROUP, + TagOptions.Builder.tag(1, Tag.DEFAULT_OWNER_KEY, Tag.DEFAULT_OWNER_VALUE).tag(2, Tag.GROUP, "group")) + ).andReturn(new Request("requestId")).anyTimes(); + expect(sshKeyPairApi.create(regionId, "prefix")) + .andReturn(new KeyPairRequest("requestId", "name", "fingerPrint", "body")).anyTimes(); + + replay(securityGroupApi, vSwitchApi, tagApi, sshKeyPairApi, vpcApi, regionAndZoneApi, api, factory, namingConvention, template, location); + + executeAndAssert(vSwitchId, securityGroupId); + } + + @Test(dependsOnMethods = "testExecuteWithSecurityGroupsVSwitchId") + public void testExecuteNoSecurityGroupsNoVSwitchId() { + String vpcId = "vpc-1"; + String vSwitchId = "vs-1"; + String securityGroupId = "sg-1"; + + vswitch = createVSwitch(vSwitchId, vpcId); + + expect(template.getOptions()).andReturn(templateOptions).anyTimes(); + + expect(securityGroupApi.list(regionId)) + .andReturn(PagedIterables.onlyPage(IterableWithMarkers.from(Lists.<SecurityGroup>newArrayList()))); + + expect(vSwitchApi.list(regionId)) + .andReturn(PagedIterables.onlyPage(IterableWithMarkers.from(Lists.<VSwitch>newArrayList()))); + + expect(regionAndZoneApi.describeZones(regionId)).andReturn(ImmutableList.of(zone)); + expect(vpcApi.create(regionId, CreateVPCOptions.Builder.vpcName(anyString()).description(anyString()))).andReturn(new VPCRequest("reqId", "routeId", "vRoutId", vpcId)); + + String vSwitchName = String.format("%s-%s", VSWITCH_PREFIX, "group"); + String vSwitchDescription = String.format("%s - %s", vSwitchName, DEFAULT_DESCRIPTION_SUFFIX); + expect(vSwitchApi.create(zone.id(), DEFAULT_CIDR_BLOCK, vpcId, + CreateVSwitchOptions.Builder.vSwitchName(vSwitchName).description(vSwitchDescription))).andReturn(new VSwitchRequest("reqId", vSwitchId)); + + expect(factory.create()).andReturn(namingConvention).anyTimes(); + expect(namingConvention.sharedNameForGroup(anyString())).andReturn("group").anyTimes(); + expect(namingConvention.uniqueNameForGroup(anyString())).andReturn("prefix").anyTimes(); + + expect(securityGroupApi.create(regionId, CreateSecurityGroupOptions.Builder.securityGroupName("group").vpcId(vpcId))) + .andReturn(new SecurityGroupRequest("requestId", securityGroupId)).anyTimes(); + expect(securityGroupApi.addInboundRule(regionId, securityGroupId, IpProtocol.TCP, "22/22", "0.0.0.0/0")).andReturn(new Request("requestId")).anyTimes(); + expect(tagApi.add(regionId, securityGroupId, ResourceType.SECURITYGROUP, + TagOptions.Builder.tag(1, Tag.DEFAULT_OWNER_KEY, Tag.DEFAULT_OWNER_VALUE).tag(2, Tag.GROUP, "group")) + ).andReturn(new Request("requestId")).anyTimes(); + expect(sshKeyPairApi.create(regionId, "prefix")).andReturn(new KeyPairRequest("requestId", "name", "fingerPrint", "body")).anyTimes(); + + replay(securityGroupApi, vSwitchApi, tagApi, sshKeyPairApi, vpcApi, regionAndZoneApi, api, factory, namingConvention, template, location); + + executeAndAssert(vSwitchId, securityGroupId); + } + + @Test(dependsOnMethods = "testExecuteNoSecurityGroupsNoVSwitchId", expectedExceptions = IllegalStateException.class) + public void testExecuteOnlySecurityGroup() { + String vpcId = "vpc-2"; + String vSwitchId = ""; + String securityGroupId = "sg-2"; + templateOptions.vSwitchId(vSwitchId).securityGroups(securityGroupId); + securityGroup = createSecurityGroup(securityGroupId, vpcId); + vswitch = createVSwitch(vSwitchId, vpcId); + + expect(template.getOptions()).andReturn(templateOptions).anyTimes(); + + expect(securityGroupApi.list(regionId)) + .andReturn(PagedIterables.onlyPage(IterableWithMarkers.from(Lists.newArrayList(securityGroup)))); + + expect(securityGroupApi.get(regionId, securityGroupId)) + .andReturn(Lists.newArrayList(permission)); + + // at least a VSwitch is available in regionId + expect(vSwitchApi.list(regionId, ListVSwitchesOptions.Builder.vpcId(vpcId))) + .andReturn(new PaginatedCollection(ImmutableMap.of("VSwitch", Lists.newArrayList(vswitch)), 1, 1, 1, regionId, "requestId")); + + expect(factory.create()).andReturn(namingConvention).anyTimes(); + expect(namingConvention.sharedNameForGroup(anyString())).andReturn("group").anyTimes(); + expect(namingConvention.uniqueNameForGroup(anyString())).andReturn("prefix").anyTimes(); + + expect(securityGroupApi.create(regionId, CreateSecurityGroupOptions.Builder.securityGroupName("group").vpcId(vpcId))) + .andReturn(new SecurityGroupRequest("requestId", securityGroupId)).anyTimes(); + expect(securityGroupApi.addInboundRule(regionId, securityGroupId, IpProtocol.TCP, "22/22", "0.0.0.0/0")) + .andReturn(new Request("requestId")).anyTimes(); + expect(tagApi.add(regionId, securityGroupId, ResourceType.SECURITYGROUP, + TagOptions.Builder.tag(1, Tag.DEFAULT_OWNER_KEY, Tag.DEFAULT_OWNER_VALUE).tag(2, Tag.GROUP, "group")) + ).andReturn(new Request("requestId")).anyTimes(); + expect(sshKeyPairApi.create(regionId, "prefix")) + .andReturn(new KeyPairRequest("requestId", "name", "fingerPrint", "body")).anyTimes(); + + replay(securityGroupApi, vSwitchApi, tagApi, sshKeyPairApi, vpcApi, regionAndZoneApi, api, factory, namingConvention, template, location); + + executeAndAssert(vSwitchId, securityGroupId); + } + + @Test(dependsOnMethods = "testExecuteOnlySecurityGroup") + public void testExecuteOnlyVSwitchId() { + String vpcId = "vpc-3"; + String vSwitchId = "vs-3"; + String securityGroupId = "sg-3"; + vswitch = createVSwitch(vSwitchId, vpcId); + + templateOptions.vSwitchId(vSwitchId).securityGroups(securityGroupId); + + expect(template.getOptions()).andReturn(templateOptions).anyTimes(); + + expect(securityGroupApi.list(regionId)) + .andReturn(PagedIterables.onlyPage(IterableWithMarkers.from(Lists.<SecurityGroup> newArrayList()))); + + expect(securityGroupApi.get(regionId, securityGroupId)) + .andReturn(Lists.newArrayList(permission)); + + // found VSwitch specified by user in VPC_PREFIX + expect(vSwitchApi.list(regionId, ListVSwitchesOptions.Builder.vSwitchId(vSwitchId))) + .andReturn(new PaginatedCollection(ImmutableMap.<String, Iterable<VSwitch>>of("VSwitch", Lists.<VSwitch> newArrayList(vswitch)), 1, 1, 1, regionId, "requestId")); + + expect(factory.create()).andReturn(namingConvention).anyTimes(); + expect(namingConvention.sharedNameForGroup(anyString())).andReturn("group").anyTimes(); + expect(namingConvention.uniqueNameForGroup(anyString())).andReturn("prefix").anyTimes(); + + expect(securityGroupApi.create(regionId, CreateSecurityGroupOptions.Builder.securityGroupName("group").vpcId(vpcId))) + .andReturn(new SecurityGroupRequest("requestId", securityGroupId)).anyTimes(); + expect(securityGroupApi.addInboundRule(regionId, securityGroupId, IpProtocol.TCP, "22/22", "0.0.0.0/0")) + .andReturn(new Request("requestId")).anyTimes(); + expect(tagApi.add(regionId, securityGroupId, ResourceType.SECURITYGROUP, + TagOptions.Builder.tag(1, Tag.DEFAULT_OWNER_KEY, Tag.DEFAULT_OWNER_VALUE).tag(2, Tag.GROUP, "group")) + ).andReturn(new Request("requestId")).anyTimes(); + expect(sshKeyPairApi.create(regionId, "prefix")) + .andReturn(new KeyPairRequest("requestId", "name", "fingerPrint", "body")).anyTimes(); + + replay(securityGroupApi, vSwitchApi, tagApi, sshKeyPairApi, vpcApi, regionAndZoneApi, api, factory, namingConvention, template, location); + + executeAndAssert(vSwitchId, securityGroupId); + } + + private void executeAndAssert(String vSwitchId, String securityGroupId) { + createResourcesThenCreateNodes.execute("group", 0, template, Collections.<NodeMetadata>emptySet(), + Collections.<NodeMetadata, Exception>emptyMap(), + ArrayListMultimap.<NodeMetadata, CustomizationResponse>create()); + + assertEquals(vSwitchId, templateOptions.getVSwitchId()); + assertEquals(securityGroupId, templateOptions.getGroups().iterator().next()); + } + + private SecurityGroup createSecurityGroup(String securityGroupId, String vpcId) { + return SecurityGroup.create( + securityGroupId, + "", + "securityGroupName", + vpcId, + ImmutableMap.<String, List<Tag>>of() + ); + } + + private VSwitch createVSwitch(String vSwitchId, String vpcId) { + return VSwitch.create( + "", + new Date(), + "vSwitch", + "", + VSwitch.Status.AVAILABLE, + 1, + vpcId, + vSwitchId, + "" + ); + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/resources/availableZones.json ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/test/resources/availableZones.json b/aliyun-ecs/src/test/resources/availableZones.json new file mode 100644 index 0000000..0b2e092 --- /dev/null +++ b/aliyun-ecs/src/test/resources/availableZones.json @@ -0,0 +1,423 @@ +{ + "RequestId": "C51117E9-1989-4407-AAB6-5E3E433E9A75", + "AvailableZones": { + "AvailableZone": [ + { + "Status": "Available", + "RegionId": "eu-central-1", + "AvailableResources": { + "AvailableResource": [ + { + "Type": "InstanceType", + "SupportedResources": { + "SupportedResource": [ + { + "Status": "Available", + "Value": "ecs.sn1ne.3xlarge" + }, + { + "Status": "Available", + "Value": "ecs.sn1ne.2xlarge" + }, + { + "Status": "Available", + "Value": "ecs.t5-c1m1.xlarge" + }, + { + "Status": "Available", + "Value": "ecs.se1ne.xlarge" + }, + { + "Status": "Available", + "Value": "ecs.t5-c1m4.xlarge" + }, + { + "Status": "Available", + "Value": "ecs.t5-c1m4.2xlarge" + }, + { + "Status": "Available", + "Value": "ecs.t5-lc1m2.large" + }, + { + "Status": "Available", + "Value": "ecs.hfg5.large" + }, + { + "Status": "Available", + "Value": "ecs.hfc5.3xlarge" + }, + { + "Status": "Available", + "Value": "ecs.hfg5.2xlarge" + }, + { + "Status": "Available", + "Value": "ecs.t5-c1m2.xlarge" + }, + { + "Status": "Available", + "Value": "ecs.t5-c1m1.2xlarge" + }, + { + "Status": "Available", + "Value": "ecs.sn2ne.xlarge" + }, + { + "Status": "Available", + "Value": "ecs.hfc5.2xlarge" + }, + { + "Status": "Available", + "Value": "ecs.t5-lc1m4.large" + }, + { + "Status": "Available", + "Value": "ecs.sn2ne.3xlarge" + }, + { + "Status": "Available", + "Value": "ecs.se1ne.2xlarge" + }, + { + "Status": "Available", + "Value": "ecs.se1ne.3xlarge" + }, + { + "Status": "Available", + "Value": "ecs.hfc5.large" + }, + { + "Status": "Available", + "Value": "ecs.hfg5.xlarge" + }, + { + "Status": "Available", + "Value": "ecs.hfc5.xlarge" + }, + { + "Status": "Available", + "Value": "ecs.t5-lc1m1.small" + }, + { + "Status": "SoldOut", + "Value": "ecs.hfg5.3xlarge" + }, + { + "Status": "Available", + "Value": "ecs.t5-lc1m2.small" + }, + { + "Status": "Available", + "Value": "ecs.t5-c1m1.large" + }, + { + "Status": "Available", + "Value": "ecs.se1ne.large" + }, + { + "Status": "Available", + "Value": "ecs.t5-lc2m1.nano" + }, + { + "Status": "Available", + "Value": "ecs.sn1ne.xlarge" + }, + { + "Status": "Available", + "Value": "ecs.sn2ne.2xlarge" + }, + { + "Status": "Available", + "Value": "ecs.sn1ne.large" + }, + { + "Status": "Available", + "Value": "ecs.t5-c1m2.2xlarge" + }, + { + "Status": "Available", + "Value": "ecs.sn2ne.large" + } + ] + } + } + ] + }, + "ZoneId": "eu-central-1b" + }, + { + "Status": "Available", + "RegionId": "eu-central-1", + "AvailableResources": { + "AvailableResource": [ + { + "Type": "InstanceType", + "SupportedResources": { + "SupportedResource": [ + { + "Status": "Available", + "Value": "ecs.sn1ne.3xlarge" + }, + { + "Status": "Available", + "Value": "ecs.n4.xlarge" + }, + { + "Status": "Available", + "Value": "ecs.sn1ne.2xlarge" + }, + { + "Status": "Available", + "Value": "ecs.t5-lc1m2.large" + }, + { + "Status": "Available", + "Value": "ecs.t5-c1m4.2xlarge" + }, + { + "Status": "Available", + "Value": "ecs.hfg5.large" + }, + { + "Status": "Available", + "Value": "ecs.hfc5.3xlarge" + }, + { + "Status": "Available", + "Value": "ecs.mn4.2xlarge" + }, + { + "Status": "Available", + "Value": "ecs.se1.2xlarge" + }, + { + "Status": "Available", + "Value": "ecs.i1.3xlarge" + }, + { + "Status": "Available", + "Value": "ecs.mn4.large" + }, + { + "Status": "Available", + "Value": "ecs.t5-c1m2.xlarge" + }, + { + "Status": "Available", + "Value": "ecs.e4.small" + }, + { + "Status": "Available", + "Value": "ecs.t5-c1m1.2xlarge" + }, + { + "Status": "Available", + "Value": "ecs.sn2ne.xlarge" + }, + { + "Status": "Available", + "Value": "ecs.hfg5.3xlarge" + }, + { + "Status": "Available", + "Value": "ecs.t5-lc1m4.large" + }, + { + "Status": "Available", + "Value": "ecs.mn4.xlarge" + }, + { + "Status": "Available", + "Value": "ecs.n4.small" + }, + { + "Status": "Available", + "Value": "ecs.i1.2xlarge" + }, + { + "Status": "Available", + "Value": "ecs.se1ne.2xlarge" + }, + { + "Status": "Available", + "Value": "ecs.sn2.large" + }, + { + "Status": "SoldOut", + "Value": "ecs.ce4.2xlarge" + }, + { + "Status": "Available", + "Value": "ecs.hfc5.large" + }, + { + "Status": "Available", + "Value": "ecs.ce4.xlarge" + }, + { + "Status": "Available", + "Value": "ecs.hfg5.xlarge" + }, + { + "Status": "Available", + "Value": "ecs.mn4.small" + }, + { + "Status": "Available", + "Value": "ecs.hfc5.xlarge" + }, + { + "Status": "Available", + "Value": "ecs.t5-lc1m2.small" + }, + { + "Status": "Available", + "Value": "ecs.t5-c1m1.large" + }, + { + "Status": "Available", + "Value": "ecs.se1ne.large" + }, + { + "Status": "Available", + "Value": "ecs.d1.4xlarge" + }, + { + "Status": "Available", + "Value": "ecs.sn2ne.2xlarge" + }, + { + "Status": "Available", + "Value": "ecs.t5-c1m2.2xlarge" + }, + { + "Status": "Available", + "Value": "ecs.gn5-c4g1.2xlarge" + }, + { + "Status": "Available", + "Value": "ecs.sn1.large" + }, + { + "Status": "Available", + "Value": "ecs.se1ne.xlarge" + }, + { + "Status": "Available", + "Value": "ecs.t5-c1m1.xlarge" + }, + { + "Status": "Available", + "Value": "ecs.sn2.medium" + }, + { + "Status": "Available", + "Value": "ecs.t5-c1m4.xlarge" + }, + { + "Status": "Available", + "Value": "ecs.se1.xlarge" + }, + { + "Status": "Available", + "Value": "ecs.sn1.medium" + }, + { + "Status": "Available", + "Value": "ecs.n4.2xlarge" + }, + { + "Status": "Available", + "Value": "ecs.hfg5.2xlarge" + }, + { + "Status": "Available", + "Value": "ecs.gn5-c8g1.2xlarge" + }, + { + "Status": "Available", + "Value": "ecs.sn2.xlarge" + }, + { + "Status": "Available", + "Value": "ecs.hfc5.2xlarge" + }, + { + "Status": "Available", + "Value": "ecs.sn2ne.3xlarge" + }, + { + "Status": "Available", + "Value": "ecs.sn1.xlarge" + }, + { + "Status": "Available", + "Value": "ecs.i1.xlarge" + }, + { + "Status": "Available", + "Value": "ecs.se1ne.3xlarge" + }, + { + "Status": "Available", + "Value": "ecs.i2.xlarge" + }, + { + "Status": "SoldOut", + "Value": "ecs.d1.3xlarge" + }, + { + "Status": "Available", + "Value": "ecs.xn4.small" + }, + { + "Status": "Available", + "Value": "ecs.t5-lc1m1.small" + }, + { + "Status": "Available", + "Value": "ecs.d1.2xlarge" + }, + { + "Status": "Available", + "Value": "ecs.t5-lc2m1.nano" + }, + { + "Status": "Available", + "Value": "ecs.i2.2xlarge" + }, + { + "Status": "Available", + "Value": "ecs.sn1ne.xlarge" + }, + { + "Status": "Available", + "Value": "ecs.gn5-c4g1.xlarge" + }, + { + "Status": "Available", + "Value": "ecs.se1.large" + }, + { + "Status": "Available", + "Value": "ecs.n4.large" + }, + { + "Status": "Available", + "Value": "ecs.sn1ne.large" + }, + { + "Status": "Available", + "Value": "ecs.sn2ne.large" + } + ] + } + } + ] + }, + "ZoneId": "eu-central-1a" + } + ] + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/2c7db7e8/aliyun-ecs/src/test/resources/instanceStatus.json ---------------------------------------------------------------------- diff --git a/aliyun-ecs/src/test/resources/instanceStatus.json b/aliyun-ecs/src/test/resources/instanceStatus.json new file mode 100644 index 0000000..592ef16 --- /dev/null +++ b/aliyun-ecs/src/test/resources/instanceStatus.json @@ -0,0 +1,18 @@ +{ + "PageNumber": 1, + "InstanceStatuses": { + "InstanceStatus": [ + { + "Status": "Stopped", + "InstanceId": "i-gw8dvsswhc7iifle1hp7" + }, + { + "Status": "Stopped", + "InstanceId": "i-gw8g25hfbxhyykn3jevq" + } + ] + }, + "TotalCount": 2, + "PageSize": 10, + "RequestId": "BDE228AF-8B12-439B-8C1C-FB42CCEB4A65" +} \ No newline at end of file
