mirror of https://github.com/apache/jclouds.git
Revert 428b2bd2ea
as this hides inconsistency between regions and zones.
This commit is contained in:
parent
6242eae0f4
commit
95141ff877
|
@ -91,6 +91,18 @@
|
|||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp</groupId>
|
||||
<artifactId>mockwebserver</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<!-- Already provided by jclouds-sshj -->
|
||||
<exclusion>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
|
|
|
@ -19,74 +19,41 @@ package org.jclouds.ec2.suppliers;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.jclouds.ec2.EC2Api;
|
||||
import org.jclouds.ec2.domain.AvailabilityZoneInfo;
|
||||
import org.jclouds.ec2.features.AvailabilityZoneAndRegionApi;
|
||||
import org.jclouds.http.HttpResponseException;
|
||||
import org.jclouds.location.Region;
|
||||
import org.jclouds.location.suppliers.RegionIdToZoneIdsSupplier;
|
||||
import org.jclouds.logging.Logger;
|
||||
import org.jclouds.util.Suppliers2;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.base.Suppliers;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableMap.Builder;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
@Singleton
|
||||
public class DescribeAvailabilityZonesInRegion implements RegionIdToZoneIdsSupplier {
|
||||
@Resource
|
||||
protected Logger logger = Logger.NULL;
|
||||
|
||||
private final AvailabilityZoneAndRegionApi client;
|
||||
public final class DescribeAvailabilityZonesInRegion implements RegionIdToZoneIdsSupplier {
|
||||
private final EC2Api api;
|
||||
private final Supplier<Set<String>> regions;
|
||||
|
||||
@Inject
|
||||
public DescribeAvailabilityZonesInRegion(EC2Api client, @Region Supplier<Set<String>> regions) {
|
||||
this.client = client.getAvailabilityZoneAndRegionApi().get();
|
||||
DescribeAvailabilityZonesInRegion(EC2Api api, @Region Supplier<Set<String>> regions) {
|
||||
this.api = api;
|
||||
this.regions = regions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Supplier<Set<String>>> get() {
|
||||
Builder<String, Set<String>> map = ImmutableMap.builder();
|
||||
HttpResponseException exception = null;
|
||||
// TODO: this should be parallel
|
||||
AvailabilityZoneAndRegionApi zoneApi = api.getAvailabilityZoneAndRegionApi().get();
|
||||
Builder<String, Supplier<Set<String>>> map = ImmutableMap.builder();
|
||||
for (String region : regions.get()) {
|
||||
try {
|
||||
ImmutableSet<String> zones = ImmutableSet.copyOf(Iterables.transform(client
|
||||
.describeAvailabilityZonesInRegion(region), new Function<AvailabilityZoneInfo, String>() {
|
||||
|
||||
@Override
|
||||
public String apply(AvailabilityZoneInfo arg0) {
|
||||
return arg0.getZone();
|
||||
}
|
||||
|
||||
}));
|
||||
if (zones.size() > 0)
|
||||
map.put(region, zones);
|
||||
} catch (HttpResponseException e) {
|
||||
// TODO: this should be in retry handler, not here.
|
||||
if (e.getMessage().contains("Unable to tunnel through proxy")) {
|
||||
exception = e;
|
||||
logger.error(e, "Could not describe availability zones in Region: %s", region);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
ImmutableSet.Builder<String> zoneBuilder = ImmutableSet.builder();
|
||||
for (AvailabilityZoneInfo zone : zoneApi.describeAvailabilityZonesInRegion(region)) {
|
||||
zoneBuilder.add(zone.getZone());
|
||||
}
|
||||
map.put(region, Suppliers.<Set<String>>ofInstance(zoneBuilder.build()));
|
||||
}
|
||||
ImmutableMap<String, Set<String>> result = map.build();
|
||||
if (result.isEmpty() && exception != null) {
|
||||
throw exception;
|
||||
}
|
||||
return Maps.transformValues(result, Suppliers2.<Set<String>> ofInstanceFunction());
|
||||
return map.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* 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.ec2.internal;
|
||||
|
||||
import static com.google.common.base.Throwables.propagate;
|
||||
import static com.google.common.net.HttpHeaders.CONTENT_TYPE;
|
||||
import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor;
|
||||
import static javax.ws.rs.core.MediaType.APPLICATION_XML;
|
||||
import static org.jclouds.util.Strings2.toStringAndClose;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jclouds.Constants;
|
||||
import org.jclouds.ContextBuilder;
|
||||
import org.jclouds.concurrent.config.ExecutorServiceModule;
|
||||
import org.jclouds.ec2.EC2Api;
|
||||
import org.jclouds.ec2.EC2ApiMetadata;
|
||||
import org.testng.annotations.AfterMethod;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.inject.Module;
|
||||
import com.squareup.okhttp.mockwebserver.MockResponse;
|
||||
import com.squareup.okhttp.mockwebserver.MockWebServer;
|
||||
import com.squareup.okhttp.mockwebserver.RecordedRequest;
|
||||
|
||||
/**
|
||||
* Tests need to run {@code singleThreaded = true) as otherwise tests will clash on the regionToServers field.
|
||||
* Sharing the regionToServers field means less code to write.
|
||||
*/
|
||||
public class BaseEC2ApiMockTest {
|
||||
protected static final String DEFAULT_REGION = "us-east-1";
|
||||
|
||||
// Example keys from http://docs.aws.amazon.com/general/latest/gr/signature-version-2.html
|
||||
private static final String ACCESS_KEY = "AKIAIOSFODNN7EXAMPLE";
|
||||
private static final String SECRET_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY";
|
||||
|
||||
private Map<String, MockWebServer> regionToServers = Maps.newLinkedHashMap();
|
||||
|
||||
protected EC2Api api() {
|
||||
return builder().buildApi(EC2Api.class);
|
||||
}
|
||||
|
||||
protected ContextBuilder builder() {
|
||||
Properties overrides = new Properties();
|
||||
overrides.setProperty(Constants.PROPERTY_MAX_RETRIES, "1");
|
||||
return ContextBuilder.newBuilder(new EC2ApiMetadata()).credentials(ACCESS_KEY, SECRET_KEY)
|
||||
.endpoint("http://localhost:" + regionToServers.get(DEFAULT_REGION).getPort()).overrides(overrides)
|
||||
.modules(modules);
|
||||
}
|
||||
|
||||
private final Set<Module> modules = ImmutableSet.<Module>of(new ExecutorServiceModule(sameThreadExecutor()));
|
||||
|
||||
@BeforeMethod
|
||||
public void start() throws IOException {
|
||||
MockWebServer server = new MockWebServer();
|
||||
server.play();
|
||||
regionToServers.put(DEFAULT_REGION, server);
|
||||
}
|
||||
|
||||
@AfterMethod(alwaysRun = true)
|
||||
public void stop() throws IOException {
|
||||
for (MockWebServer server : regionToServers.values()) {
|
||||
server.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
protected void enqueue(String region, MockResponse response) {
|
||||
regionToServers.get(region).enqueue(response);
|
||||
}
|
||||
|
||||
protected void enqueueRegions(String... regions) throws IOException {
|
||||
StringBuilder describeRegionsResponse = new StringBuilder();
|
||||
describeRegionsResponse.append("<DescribeRegionsResponse>");
|
||||
for (String region : regions) {
|
||||
describeRegionsResponse.append("<item>");
|
||||
describeRegionsResponse.append("<regionName>").append(region).append("</regionName>");
|
||||
if (!regionToServers.containsKey(region)) {
|
||||
MockWebServer server = new MockWebServer();
|
||||
server.play();
|
||||
regionToServers.put(region, server);
|
||||
}
|
||||
String regionEndpoint = "http://localhost:" + regionToServers.get(region).getPort();
|
||||
describeRegionsResponse.append("<regionEndpoint>").append(regionEndpoint).append("</regionEndpoint>");
|
||||
describeRegionsResponse.append("</item>");
|
||||
}
|
||||
describeRegionsResponse.append("</DescribeRegionsResponse>");
|
||||
enqueue(DEFAULT_REGION,
|
||||
new MockResponse().addHeader(CONTENT_TYPE, APPLICATION_XML).setBody(describeRegionsResponse.toString()));
|
||||
}
|
||||
|
||||
protected void enqueueXml(String region, String resource) {
|
||||
enqueue(region,
|
||||
new MockResponse().addHeader(CONTENT_TYPE, APPLICATION_XML).setBody(stringFromResource(resource)));
|
||||
}
|
||||
|
||||
protected String stringFromResource(String resourceName) {
|
||||
try {
|
||||
return toStringAndClose(getClass().getResourceAsStream(resourceName));
|
||||
} catch (IOException e) {
|
||||
throw propagate(e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Stripping out authorization, ensures the following post params were sent. */
|
||||
protected RecordedRequest assertPosted(String region, String postParams) throws InterruptedException {
|
||||
RecordedRequest request = regionToServers.get(region).takeRequest();
|
||||
assertEquals(request.getMethod(), "POST");
|
||||
assertEquals(request.getPath(), "/");
|
||||
assertEquals(new String(request.getBody(), Charsets.UTF_8).replaceAll("&Signature.*", ""), postParams);
|
||||
return request;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jclouds.ec2.suppliers;
|
||||
|
||||
import static org.testng.Assert.assertEquals;
|
||||
import static org.testng.Assert.fail;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jclouds.ec2.internal.BaseEC2ApiMockTest;
|
||||
import org.jclouds.rest.AuthorizationException;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.base.Suppliers;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.squareup.okhttp.mockwebserver.MockResponse;
|
||||
|
||||
@Test(groups = "unit", testName = "DescribeAvailabilityZonesInRegionMockTest", singleThreaded = true)
|
||||
public class DescribeAvailabilityZonesInRegionMockTest extends BaseEC2ApiMockTest {
|
||||
|
||||
public void onlySendsRequestsToConfiguredRegions() throws Exception {
|
||||
enqueueRegions("us-east-1");
|
||||
enqueueXml("us-east-1", "/availabilityZones.xml");
|
||||
|
||||
Map<String, Supplier<Set<String>>> result = new DescribeAvailabilityZonesInRegion(api(),
|
||||
supplyRegionIds("us-east-1")).get();
|
||||
|
||||
assertEquals(result.size(), 1);
|
||||
assertEquals(result.get("us-east-1").get(),
|
||||
ImmutableSet.of("us-east-1a", "us-east-1b", "us-east-1c", "us-east-1d"));
|
||||
|
||||
assertPosted("us-east-1", "Action=DescribeRegions");
|
||||
assertPosted("us-east-1", "Action=DescribeAvailabilityZones");
|
||||
}
|
||||
|
||||
public void failsOnAuthorizationErrorToAnyRegion() throws Exception {
|
||||
enqueueRegions("us-east-1", "eu-central-1");
|
||||
enqueueXml("us-east-1", "/availabilityZones.xml");
|
||||
enqueue("eu-central-1", new MockResponse().setResponseCode(401));
|
||||
|
||||
DescribeAvailabilityZonesInRegion supplier = new DescribeAvailabilityZonesInRegion(api(),
|
||||
supplyRegionIds("us-east-1", "eu-central-1"));
|
||||
|
||||
try {
|
||||
supplier.get();
|
||||
fail();
|
||||
} catch (AuthorizationException e){
|
||||
|
||||
}
|
||||
|
||||
assertPosted("us-east-1", "Action=DescribeRegions");
|
||||
assertPosted("us-east-1", "Action=DescribeAvailabilityZones");
|
||||
assertPosted("eu-central-1", "Action=DescribeAvailabilityZones");
|
||||
}
|
||||
|
||||
private static Supplier<Set<String>> supplyRegionIds(String... regionIds) {
|
||||
return Suppliers.<Set<String>>ofInstance(ImmutableSet.copyOf(regionIds));
|
||||
}
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
/*
|
||||
* 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.ec2.suppliers;
|
||||
|
||||
import static org.easymock.EasyMock.expect;
|
||||
import static org.easymock.classextension.EasyMock.createControl;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
import static org.testng.Assert.fail;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.easymock.classextension.IMocksControl;
|
||||
import org.jclouds.ec2.EC2Api;
|
||||
import org.jclouds.ec2.domain.AvailabilityZoneInfo;
|
||||
import org.jclouds.ec2.features.AvailabilityZoneAndRegionApi;
|
||||
import org.jclouds.http.HttpCommand;
|
||||
import org.jclouds.http.HttpResponseException;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.base.Suppliers;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
/**
|
||||
* A test for {@link DescribeAvailabilityZonesInRegion}.
|
||||
*
|
||||
* @author Eric Pabst (pabstec@familysearch.org)
|
||||
*/
|
||||
public class DescribeAvailabilityZonesInRegionTest {
|
||||
@Test
|
||||
public void testDescribeAvailabilityZonesInRegion_BestEffort() {
|
||||
IMocksControl control = createControl();
|
||||
EC2Api client = control.createMock(EC2Api.class);
|
||||
AvailabilityZoneAndRegionApi regionClient = control.createMock(AvailabilityZoneAndRegionApi.class);
|
||||
AvailabilityZoneInfo info1 = control.createMock(AvailabilityZoneInfo.class);
|
||||
AvailabilityZoneInfo info2 = control.createMock(AvailabilityZoneInfo.class);
|
||||
HttpCommand command = control.createMock(HttpCommand.class);
|
||||
HttpResponseException exception = new HttpResponseException("Error: Unable to tunnel through proxy: ...",
|
||||
command, null);
|
||||
|
||||
expect(client.getAvailabilityZoneAndRegionApi()).andStubReturn((Optional) Optional.of(regionClient));
|
||||
expect(regionClient.describeAvailabilityZonesInRegion("accessibleRegion1")).andReturn(
|
||||
ImmutableSet.of(info1));
|
||||
expect(regionClient.describeAvailabilityZonesInRegion("inaccessibleRegion")).andThrow(exception);
|
||||
expect(regionClient.describeAvailabilityZonesInRegion("accessibleRegion2")).andReturn(
|
||||
ImmutableSet.of(info2));
|
||||
expect(info1.getZone()).andStubReturn("zone1");
|
||||
expect(info2.getZone()).andStubReturn("zone2");
|
||||
|
||||
Set<String> regions = ImmutableSet.of("accessibleRegion1", "inaccessibleRegion", "accessibleRegion2");
|
||||
control.replay();
|
||||
|
||||
Map<String, Set<String>> expectedResult = ImmutableMap.<String, Set<String>> builder().put("accessibleRegion1",
|
||||
ImmutableSet.of("zone1")).put("accessibleRegion2", ImmutableSet.of("zone2")).build();
|
||||
|
||||
DescribeAvailabilityZonesInRegion regionIdToZoneId = new DescribeAvailabilityZonesInRegion(client, Suppliers
|
||||
.ofInstance(regions));
|
||||
assertEquals(Maps.transformValues(regionIdToZoneId.get(), Suppliers.<Set<String>> supplierFunction()),
|
||||
expectedResult);
|
||||
control.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDescribeAvailabilityZonesInRegion_RethrowIfNoneFound() {
|
||||
IMocksControl control = createControl();
|
||||
EC2Api client = control.createMock(EC2Api.class);
|
||||
AvailabilityZoneAndRegionApi regionClient = control.createMock(AvailabilityZoneAndRegionApi.class);
|
||||
HttpCommand command = control.createMock(HttpCommand.class);
|
||||
HttpResponseException exception = new HttpResponseException("Error: Unable to tunnel through proxy: ...",
|
||||
command, null);
|
||||
|
||||
expect(client.getAvailabilityZoneAndRegionApi()).andStubReturn((Optional) Optional.of(regionClient));
|
||||
expect(regionClient.describeAvailabilityZonesInRegion("inaccessibleRegion")).andThrow(exception);
|
||||
|
||||
Set<String> regions = ImmutableSet.of("inaccessibleRegion");
|
||||
control.replay();
|
||||
|
||||
DescribeAvailabilityZonesInRegion regionIdToZoneId = new DescribeAvailabilityZonesInRegion(client, Suppliers
|
||||
.ofInstance(regions));
|
||||
try {
|
||||
regionIdToZoneId.get();
|
||||
fail("expected exception");
|
||||
} catch (HttpResponseException e) {
|
||||
assertEquals(e, exception);
|
||||
}
|
||||
control.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDescribeAvailabilityZonesInRegion_NoZones() {
|
||||
IMocksControl control = createControl();
|
||||
EC2Api client = control.createMock(EC2Api.class);
|
||||
AvailabilityZoneAndRegionApi regionClient = control.createMock(AvailabilityZoneAndRegionApi.class);
|
||||
|
||||
expect(client.getAvailabilityZoneAndRegionApi()).andStubReturn((Optional) Optional.of(regionClient));
|
||||
expect(regionClient.describeAvailabilityZonesInRegion("emptyRegion")).andReturn(
|
||||
ImmutableSet.<AvailabilityZoneInfo> of());
|
||||
|
||||
Set<String> regions = ImmutableSet.of("emptyRegion");
|
||||
control.replay();
|
||||
|
||||
DescribeAvailabilityZonesInRegion regionIdToZoneId = new DescribeAvailabilityZonesInRegion(client, Suppliers
|
||||
.ofInstance(regions));
|
||||
assertEquals(regionIdToZoneId.get(), ImmutableMap.<String, String> of());
|
||||
control.verify();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue