Revert 428b2bd2ea as this hides inconsistency between regions and zones.

This commit is contained in:
Adrian Cole 2014-11-16 12:26:35 -08:00
parent b59d7fd792
commit 0de8fe0e05
5 changed files with 232 additions and 167 deletions

View File

@ -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>

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -1,122 +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}.
*/
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();
}
}