Merge pull request #1288 from jclouds/dynect-zones

add session management filter and read-only zone support to dynect
This commit is contained in:
Adrian Cole 2013-02-01 14:44:00 -08:00
commit adf9cd5802
22 changed files with 1033 additions and 31 deletions

View File

@ -19,6 +19,7 @@
package org.jclouds.dynect.v3;
import org.jclouds.dynect.v3.features.SessionApi;
import org.jclouds.dynect.v3.features.ZoneApi;
import org.jclouds.rest.annotations.Delegate;
/**
@ -37,4 +38,10 @@ public interface DynECTApi {
*/
@Delegate
SessionApi getSessionApi();
/**
* Provides synchronous access to Zone features.
*/
@Delegate
ZoneApi getZoneApi();
}

View File

@ -23,6 +23,7 @@ import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import javax.ws.rs.Produces;
import org.jclouds.dynect.v3.features.SessionAsyncApi;
import org.jclouds.dynect.v3.features.ZoneAsyncApi;
import org.jclouds.rest.annotations.Delegate;
import org.jclouds.rest.annotations.Headers;
@ -44,4 +45,10 @@ public interface DynECTAsyncApi {
*/
@Delegate
SessionAsyncApi getSessionApi();
/**
* Provides asynchronous access to Zone features.
*/
@Delegate
ZoneAsyncApi getZoneApi();
}

View File

@ -18,6 +18,8 @@
*/
package org.jclouds.dynect.v3;
import static org.jclouds.Constants.PROPERTY_MAX_REDIRECTS;
import java.net.URI;
import java.util.Properties;
@ -49,6 +51,8 @@ public class DynECTProviderMetadata extends BaseProviderMetadata {
public static Properties defaultProperties() {
Properties properties = new Properties();
// job polling occurs via redirect loop
properties.setProperty(PROPERTY_MAX_REDIRECTS, "20");
return properties;
}

View File

@ -18,12 +18,19 @@
*/
package org.jclouds.dynect.v3.config;
import static org.jclouds.rest.config.BinderUtils.bindHttpApi;
import java.util.Map;
import org.jclouds.dynect.v3.DynECTApi;
import org.jclouds.dynect.v3.DynECTAsyncApi;
import org.jclouds.dynect.v3.features.SessionApi;
import org.jclouds.dynect.v3.features.SessionAsyncApi;
import org.jclouds.dynect.v3.features.ZoneApi;
import org.jclouds.dynect.v3.features.ZoneAsyncApi;
import org.jclouds.dynect.v3.filters.SessionManager;
import org.jclouds.http.HttpRetryHandler;
import org.jclouds.http.annotation.ClientError;
import org.jclouds.rest.ConfiguresRestClient;
import org.jclouds.rest.config.RestClientModule;
@ -39,9 +46,24 @@ public class DynECTRestClientModule extends RestClientModule<DynECTApi, DynECTAs
public static final Map<Class<?>, Class<?>> DELEGATE_MAP = ImmutableMap.<Class<?>, Class<?>> builder()
.put(SessionApi.class, SessionAsyncApi.class)
.build();
.put(ZoneApi.class, ZoneAsyncApi.class).build();
public DynECTRestClientModule() {
super(DELEGATE_MAP);
}
@Override
protected void bindRetryHandlers() {
bind(HttpRetryHandler.class).annotatedWith(ClientError.class).to(SessionManager.class);
}
@Override
protected void configure() {
// binding explicitly ensures singleton despite multiple linked bindings
bind(SessionManager.class);
super.configure();
// Bind apis that are used directly vs via DynECTApi
bindHttpApi(binder(), SessionApi.class, SessionAsyncApi.class);
}
}

View File

@ -0,0 +1,205 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, String 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.dynect.v3.domain;
import static com.google.common.base.Objects.equal;
import static com.google.common.base.Objects.toStringHelper;
import static com.google.common.base.Preconditions.checkNotNull;
import java.beans.ConstructorProperties;
import javax.inject.Named;
import com.google.common.base.CaseFormat;
import com.google.common.base.Objects;
/**
* @author Adrian Cole
*/
public final class Zone {
private final String name;
@Named("zone_type")
private final Type type;
private final int serial;
@Named("serial_style")
private final SerialStyle serialStyle;
public static enum Type {
PRIMARY, SECONDARY;
@Override
public String toString() {
return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, name());
}
public static Type fromValue(String type) {
return valueOf(CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, checkNotNull(type, "type")));
}
}
public static enum SerialStyle {
/**
* Serials are incremented by 1 on every change
*/
INCREMENT,
/**
* Serials will be the UNIX timestamp at the time of the publish
*/
EPOCH,
/**
* Serials will be in the form of YYYYMMDDxx where xx is incremented by
* one for each change during that particular day
*/
DAY,
/**
* Serials will be in the form of YYMMDDHHMM
*/
MINUTE,
UNRECOGNIZED;
@Override
public String toString() {
return name().toLowerCase();
}
public static SerialStyle fromValue(String serialStyle) {
try {
return valueOf(checkNotNull(serialStyle, "serialStyle").toUpperCase());
} catch (IllegalArgumentException e) {
return UNRECOGNIZED;
}
}
}
@ConstructorProperties({ "zone", "zone_type", "serial", "serial_style" })
private Zone(String name, Type type, int serial, SerialStyle serialStyle) {
this.name = checkNotNull(name, "name");
this.type = checkNotNull(type, "type for %s", name);
this.serial = checkNotNull(serial, "serial for %s", name);
this.serialStyle = checkNotNull(serialStyle, "serialStyle for %s", serialStyle);
}
/**
* The name of the requested zone
*/
public String getName() {
return name;
}
/**
* A unique string that identifies the request to create the hosted zone.
*/
public Type getType() {
return type;
}
/**
* The current serial number of the zone
*/
public int getSerial() {
return serial;
}
/**
* The style of the zone's serial
*/
public SerialStyle getSerialStyle() {
return serialStyle;
}
@Override
public int hashCode() {
return Objects.hashCode(name, type);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null || getClass() != obj.getClass())
return false;
Zone that = Zone.class.cast(obj);
return equal(this.name, that.name) && equal(this.type, that.type);
}
@Override
public String toString() {
return toStringHelper(this).omitNullValues().add("name", name).add("type", type).add("serial", serial)
.add("serialStyle", serialStyle).toString();
}
public static Builder builder() {
return new Builder();
}
public Builder toBuilder() {
return builder().from(this);
}
public final static class Builder {
private String name;
private Type type;
private int serial;
private SerialStyle serialStyle;
/**
* @see Zone#getName()
*/
public Builder name(String name) {
this.name = name;
return this;
}
/**
* @see Zone#getType()
*/
public Builder type(Type type) {
this.type = type;
return this;
}
/**
* @see Zone#getSerial()
*/
public Builder serial(int serial) {
this.serial = serial;
return this;
}
/**
* @see Zone#getSerialStyle()
*/
public Builder serialStyle(SerialStyle serialStyle) {
this.serialStyle = serialStyle;
return this;
}
public Zone build() {
return new Zone(name, type, serial, serialStyle);
}
public Builder from(Zone in) {
return this.name(in.name).type(in.type).serial(in.serial).serialStyle(in.serialStyle);
}
}
}

View File

@ -0,0 +1,47 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, String 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.dynect.v3.features;
import org.jclouds.dynect.v3.domain.Zone;
import org.jclouds.javax.annotation.Nullable;
import com.google.common.collect.FluentIterable;
/**
* @see ZoneAsyncApi
* @author Adrian Cole
*/
public interface ZoneApi {
/**
* Lists all zone ids.
*/
FluentIterable<String> list();
/**
* Retrieves information about the specified zone, including its nameserver
* configuration
*
* @param name
* name of the zone to get information about. ex
* {@code Z1PA6795UKMFR9}
* @return null if not found
*/
@Nullable
Zone get(String name);
}

View File

@ -0,0 +1,74 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, String 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.dynect.v3.features;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import javax.inject.Named;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import org.jclouds.Fallbacks.NullOnNotFoundOr404;
import org.jclouds.dynect.v3.domain.Zone;
import org.jclouds.dynect.v3.filters.SessionManager;
import org.jclouds.dynect.v3.functions.ExtractNames;
import org.jclouds.rest.annotations.Fallback;
import org.jclouds.rest.annotations.Headers;
import org.jclouds.rest.annotations.RequestFilters;
import org.jclouds.rest.annotations.SelectJson;
import org.jclouds.rest.annotations.Transform;
import com.google.common.collect.FluentIterable;
import com.google.common.util.concurrent.ListenableFuture;
/**
*
* @see ZoneApi
* @see <a
* href="https://manage.dynect.net/help/docs/api2/rest/resources/Zone.html">doc</a>
* @author Adrian Cole
*/
// required for all calls
@Produces(APPLICATION_JSON)
@Headers(keys = "API-Version", values = "{jclouds.api-version}")
@Path("/Zone")
@RequestFilters(SessionManager.class)
public interface ZoneAsyncApi {
/**
* @see ZoneApi#list
*/
@Named("GET:ZoneList")
@GET
@SelectJson("data")
@Transform(ExtractNames.class)
ListenableFuture<FluentIterable<String>> list();
/**
* @see ZoneApi#isValid
*/
@Named("GET:Zone")
@GET
@Path("/{name}")
@SelectJson("data")
@Fallback(NullOnNotFoundOr404.class)
ListenableFuture<Zone> get(@PathParam("name") String name);
}

View File

@ -0,0 +1,134 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.dynect.v3.filters;
import static org.jclouds.http.HttpUtils.closeClientButKeepContentStream;
import static org.jclouds.http.HttpUtils.releasePayload;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import org.jclouds.domain.Credentials;
import org.jclouds.dynect.v3.domain.Session;
import org.jclouds.dynect.v3.domain.SessionCredentials;
import org.jclouds.dynect.v3.features.SessionApi;
import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpException;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpRequest.Builder;
import org.jclouds.http.HttpRequestFilter;
import org.jclouds.http.HttpResponse;
import org.jclouds.http.handlers.BackoffLimitedRetryHandler;
import org.jclouds.location.Provider;
import org.jclouds.logging.Logger;
import com.google.common.base.Supplier;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.inject.Inject;
import com.google.inject.Singleton;
/**
*
* This class manages session interactions, including grabbing latest from the
* cache, and invalidating upon 401 If the credentials supplied in the
* authentication header are invalid, or if the token has expired, the server
* returns HTTP response code 401. After the token expires, you must log in
* again to obtain a new token.
*
* @author Adrian Cole
*
*/
@Singleton
public final class SessionManager extends BackoffLimitedRetryHandler implements HttpRequestFilter {
@Resource
private Logger logger = Logger.NULL;
private final Supplier<Credentials> creds;
private final SessionApi sessionApi;
private final LoadingCache<Credentials, Session> sessionCache;
@Inject
SessionManager(@Provider Supplier<Credentials> creds, SessionApi sessionApi) {
this(creds, buildCache(sessionApi), sessionApi);
}
SessionManager(@Provider Supplier<Credentials> creds, LoadingCache<Credentials, Session> sessionCache,
SessionApi sessionApi) {
this.creds = creds;
this.sessionCache = sessionCache;
this.sessionApi = sessionApi;
}
static LoadingCache<Credentials, Session> buildCache(final SessionApi sessionApi) {
return CacheBuilder.newBuilder().build(new CacheLoader<Credentials, Session>() {
public Session load(Credentials key) {
return sessionApi.login(convert(key));
}
});
}
static SessionCredentials convert(Credentials key) {
if (key instanceof SessionCredentials)
return SessionCredentials.class.cast(key);
return SessionCredentials.builder().customerName(key.identity.substring(0, key.identity.indexOf(':')))
.userName(key.identity.substring(key.identity.indexOf(':') + 1)).password(key.credential).build();
}
@Override
public HttpRequest filter(HttpRequest request) throws HttpException {
Session session = sessionCache.getUnchecked(creds.get());
Builder<?> builder = request.toBuilder();
builder.replaceHeader("Auth-Token", session.getToken());
return builder.build();
}
@Override
public boolean shouldRetryRequest(HttpCommand command, HttpResponse response) {
boolean retry = false; // default
try {
if (response.getStatusCode() == 401) {
closeClientButKeepContentStream(response);
logger.debug("invalidating session");
sessionCache.invalidateAll();
retry = super.shouldRetryRequest(command, response);
}
return retry;
} finally {
releasePayload(response);
}
}
/**
* it is important that we close any sessions on close to help the server not
* become overloaded.
*/
@PreDestroy
public void logoutOnClose() {
for (Session s : sessionCache.asMap().values()) {
try {
sessionApi.logout(s.getToken());
} catch (Exception e) {
logger.error(e, "error logging out session %s", s);
}
}
}
}

View File

@ -0,0 +1,45 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, String 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.dynect.v3.functions;
import com.google.common.base.Function;
import com.google.common.collect.FluentIterable;
/**
* Zones come back encoded in REST paths, such as
* {@code /REST/Zone/jclouds.org/}
*
* @author Adrian Cole
*
*/
public final class ExtractNames implements Function<FluentIterable<String>, FluentIterable<String>> {
public FluentIterable<String> apply(FluentIterable<String> in) {
return in.transform(ExtractNameInPath.INSTANCE);
}
static enum ExtractNameInPath implements Function<String, String> {
INSTANCE;
final int position = "/REST/Zone/".length();
public String apply(String in) {
return in.substring(position, in.length() - 1);
}
}
}

View File

@ -0,0 +1,48 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.dynect.v3.loaders;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.jclouds.dynect.v3.domain.Session;
import org.jclouds.dynect.v3.domain.SessionCredentials;
import org.jclouds.dynect.v3.features.SessionApi;
import com.google.common.cache.CacheLoader;
@Singleton
public class LoginUserInOrgWithPassword extends CacheLoader<SessionCredentials, Session> {
private final SessionApi api;
@Inject
LoginUserInOrgWithPassword(SessionApi api) {
this.api = api;
}
@Override
public Session load(SessionCredentials input) {
return api.login(input);
}
@Override
public String toString() {
return "loginUserInOrgWithPassword()";
}
}

View File

@ -26,7 +26,7 @@ import static org.testng.Assert.assertTrue;
import org.jclouds.dynect.v3.DynECTApi;
import org.jclouds.dynect.v3.domain.SessionCredentials;
import org.jclouds.dynect.v3.internal.BaseDynECTApiExpectTest;
import org.jclouds.dynect.v3.parse.ParseSessionTest;
import org.jclouds.dynect.v3.parse.CreateSessionResponseTest;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse;
import org.testng.annotations.Test;
@ -37,24 +37,13 @@ import org.testng.annotations.Test;
@Test(groups = "unit", testName = "SessionApiExpectTest")
public class SessionApiExpectTest extends BaseDynECTApiExpectTest {
private String authToken = "FFFFFFFFFF";
HttpRequest create = HttpRequest.builder().method("POST")
.endpoint("https://api2.dynect.net/REST/Session")
.addHeader("API-Version", "3.3.7")
.payload(payloadFromStringWithContentType("{\"customer_name\":\"jclouds\",\"user_name\":\"joe\",\"password\":\"letmein\"}",APPLICATION_JSON))
.build();
HttpResponse createResponse = HttpResponse.builder().statusCode(200)
.payload(payloadFromResourceWithContentType("/create_session.json", APPLICATION_JSON)).build();
public void testCreateWhenResponseIs2xx() {
DynECTApi apiCreatesSession = requestSendsResponse(create, createResponse);
DynECTApi apiCreatesSession = requestSendsResponse(createSession, createSessionResponse);
assertEquals(apiCreatesSession.getSessionApi().login(SessionCredentials.builder()
.customerName("jclouds")
.userName("joe")
.password("letmein").build()).toString(),
new ParseSessionTest().expected().toString());
new CreateSessionResponseTest().expected().toString());
}
HttpRequest isValid = HttpRequest.builder().method("GET")

View File

@ -0,0 +1,75 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, String 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.dynect.v3.features;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;
import org.jclouds.dynect.v3.DynECTApi;
import org.jclouds.dynect.v3.internal.BaseDynECTApiExpectTest;
import org.jclouds.dynect.v3.parse.GetZoneResponseTest;
import org.jclouds.dynect.v3.parse.ListZonesResponseTest;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse;
import org.testng.annotations.Test;
/**
* @author Adrian Cole
*/
@Test(groups = "unit", testName = "ZoneApiExpectTest")
public class ZoneApiExpectTest extends BaseDynECTApiExpectTest {
HttpRequest get = HttpRequest.builder().method("GET")
.endpoint("https://api2.dynect.net/REST/Zone/jclouds.org")
.addHeader("API-Version", "3.3.7")
.addHeader("Auth-Token", authToken)
.payload(emptyJsonPayload())
.build();
HttpResponse getResponse = HttpResponse.builder().statusCode(200)
.payload(payloadFromResourceWithContentType("/get_zone.json", APPLICATION_JSON)).build();
public void testGetWhenResponseIs2xx() {
DynECTApi success = requestsSendResponses(createSession, createSessionResponse, get, getResponse);
assertEquals(success.getZoneApi().get("jclouds.org").toString(),
new GetZoneResponseTest().expected().toString());
}
public void testGetWhenResponseError2401() {
DynECTApi fail = requestsSendResponses(createSession, createSessionResponse, get, notFound);
assertNull(fail.getZoneApi().get("jclouds.org"));
}
HttpRequest list = HttpRequest.builder().method("GET")
.endpoint("https://api2.dynect.net/REST/Zone")
.addHeader("API-Version", "3.3.7")
.addHeader("Auth-Token", authToken)
.payload(emptyJsonPayload())
.build();
HttpResponse listResponse = HttpResponse.builder().statusCode(200)
.payload(payloadFromResourceWithContentType("/list_zones.json", APPLICATION_JSON)).build();
public void testListWhenResponseIs2xx() {
DynECTApi success = requestsSendResponses(createSession, createSessionResponse, list, listResponse);
assertEquals(success.getZoneApi().list().toString(),
new ListZonesResponseTest().expected().toString());
}
}

View File

@ -0,0 +1,62 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, String 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.dynect.v3.features;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.logging.Logger.getAnonymousLogger;
import static org.testng.Assert.assertNull;
import org.jclouds.dynect.v3.domain.Zone;
import org.jclouds.dynect.v3.internal.BaseDynECTApiLiveTest;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
/**
* @author Adrian Cole
*/
@Test(groups = "live", testName = "ZoneApiLiveTest")
public class ZoneApiLiveTest extends BaseDynECTApiLiveTest {
private void checkZone(Zone zone) {
checkNotNull(zone.getName(), "Name cannot be null for a Zone: %s", zone);
checkNotNull(zone.getSerial(), "Serial cannot be null for a Zone: %s", zone);
}
@Test
protected void testListAndGetZones() {
ImmutableList<String> zones = api().list().toList();
getAnonymousLogger().info("zones: " + zones.size());
for (String zoneName : zones) {
Zone zone = api().get(zoneName);
checkNotNull(zone, "zone was null for Zone: %s", zoneName);
checkZone(zone);
}
}
@Test
public void testGetZoneWhenNotFound() {
assertNull(api().get("AAAAAAAAAAAAAAAA"));
}
protected ZoneApi api() {
return context.getApi().getZoneApi();
}
}

View File

@ -0,0 +1,113 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.dynect.v3.filters;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertSame;
import static org.testng.Assert.assertTrue;
import org.jclouds.domain.Credentials;
import org.jclouds.dynect.v3.domain.Session;
import org.jclouds.dynect.v3.domain.SessionCredentials;
import org.jclouds.dynect.v3.features.SessionApi;
import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpResponse;
import org.testng.annotations.Test;
import com.google.common.base.Supplier;
import com.google.common.cache.LoadingCache;
/**
* @author Adrian Cole
*/
@Test(groups = "unit", testName = "SessionManagerTest")
public class SessionManagerTest {
SessionCredentials creds = SessionCredentials.builder().customerName("customer").userName("robbie")
.password("password").build();
Session session = Session.forTokenAndVersion("token", "version");
public void testAlreadySessionCredentials() {
assertSame(SessionManager.convert(creds), creds);
}
public void testConvertCredentialsParsesCustomer() {
assertEquals(SessionManager.convert(new Credentials("customer:robbie", "password")), creds);
}
public void testCacheLoadLogsIn() {
SessionApi sessionApi = createMock(SessionApi.class);
expect(sessionApi.login(creds)).andReturn(session);
replay(sessionApi);
assertSame(SessionManager.buildCache(sessionApi).apply(creds), session);
verify(sessionApi);
}
@SuppressWarnings("unchecked")
@Test
public void test401ShouldInvalidateSessionAndRetry() {
HttpCommand command = createMock(HttpCommand.class);
Supplier<Credentials> creds = createMock(Supplier.class);
LoadingCache<Credentials, Session> sessionCache = createMock(LoadingCache.class);
SessionApi sessionApi = createMock(SessionApi.class);
sessionCache.invalidateAll();
expectLastCall();
expect(command.incrementFailureCount()).andReturn(1);
expect(command.isReplayable()).andReturn(true);
expect(command.getFailureCount()).andReturn(1).atLeastOnce();
replay(creds, sessionCache, sessionApi, command);
HttpResponse response = HttpResponse.builder().statusCode(401).build();
SessionManager retry = new SessionManager(creds, sessionCache, sessionApi);
assertTrue(retry.shouldRetryRequest(command, response));
verify(creds, sessionCache, sessionApi, command);
}
@SuppressWarnings("unchecked")
@Test
public void test403ShouldNotInvalidateSessionOrRetry() {
HttpCommand command = createMock(HttpCommand.class);
Supplier<Credentials> creds = createMock(Supplier.class);
LoadingCache<Credentials, Session> sessionCache = createMock(LoadingCache.class);
SessionApi sessionApi = createMock(SessionApi.class);
replay(creds, sessionCache, sessionApi, command);
HttpResponse response = HttpResponse.builder().statusCode(403).build();
SessionManager retry = new SessionManager(creds, sessionCache, sessionApi);
assertFalse(retry.shouldRetryRequest(command, response));
verify(creds, sessionCache, sessionApi, command);
}
}

View File

@ -0,0 +1,45 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.dynect.v3.functions;
import static org.testng.Assert.assertEquals;
import org.jclouds.dynect.v3.functions.ExtractNames.ExtractNameInPath;
import org.testng.annotations.Test;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableSet;
/**
* @author Adrian Cole
*/
@Test(groups = "unit")
public class ExtractNamesTest {
ExtractNames fn = new ExtractNames();
public void testExtractNameInPath() {
assertEquals(ExtractNameInPath.INSTANCE.apply("/REST/Zone/jclouds.org/"), "jclouds.org");
}
public void testExtractNames() {
assertEquals(fn.apply(FluentIterable.from(ImmutableSet.of("/REST/Zone/jclouds.org/"))).toSet(),
ImmutableSet.of("jclouds.org"));
}
}

View File

@ -18,12 +18,7 @@
*/
package org.jclouds.dynect.v3.internal;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import org.jclouds.dynect.v3.DynECTApi;
import org.jclouds.http.HttpRequest;
import org.jclouds.io.Payload;
import org.jclouds.io.Payloads;
/**
* Base class for writing DynECT Expect tests
@ -32,14 +27,4 @@ import org.jclouds.io.Payloads;
*/
public class BaseDynECTApiExpectTest extends BaseDynECTExpectTest<DynECTApi> {
public static Payload emptyJsonPayload() {
Payload p = Payloads.newByteArrayPayload(new byte[] {});
p.getContentMetadata().setContentType(APPLICATION_JSON);
return p;
}
@Override
protected HttpRequestComparisonType compareHttpRequestAsType(HttpRequest input) {
return HttpRequestComparisonType.JSON;
}
}

View File

@ -18,6 +18,12 @@
*/
package org.jclouds.dynect.v3.internal;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse;
import org.jclouds.io.Payload;
import org.jclouds.io.Payloads;
import org.jclouds.rest.internal.BaseRestApiExpectTest;
/**
@ -28,5 +34,31 @@ import org.jclouds.rest.internal.BaseRestApiExpectTest;
public class BaseDynECTExpectTest<T> extends BaseRestApiExpectTest<T> {
public BaseDynECTExpectTest() {
provider = "dynect";
identity = "jclouds:joe";
credential = "letmein";
}
public static Payload emptyJsonPayload() {
Payload p = Payloads.newByteArrayPayload(new byte[] {});
p.getContentMetadata().setContentType(APPLICATION_JSON);
return p;
}
@Override
protected HttpRequestComparisonType compareHttpRequestAsType(HttpRequest input) {
return HttpRequestComparisonType.JSON;
}
protected String authToken = "FFFFFFFFFF";
protected HttpRequest createSession = HttpRequest.builder().method("POST")
.endpoint("https://api2.dynect.net/REST/Session")
.addHeader("API-Version", "3.3.7")
.payload(payloadFromStringWithContentType("{\"customer_name\":\"jclouds\",\"user_name\":\"joe\",\"password\":\"letmein\"}",APPLICATION_JSON))
.build();
protected HttpResponse createSessionResponse = HttpResponse.builder().statusCode(200)
.payload(payloadFromResourceWithContentType("/create_session.json", APPLICATION_JSON)).build();
protected HttpResponse notFound = HttpResponse.builder().statusCode(404).build();
}

View File

@ -31,7 +31,7 @@ import org.testng.annotations.Test;
* @author Adrian Cole
*/
@Test(groups = "unit")
public class ParseSessionTest extends BaseDynECTParseTest<Session> {
public class CreateSessionResponseTest extends BaseDynECTParseTest<Session> {
@Override
public String resource() {

View File

@ -0,0 +1,49 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.dynect.v3.parse;
import static org.jclouds.dynect.v3.domain.Zone.SerialStyle.INCREMENT;
import static org.jclouds.dynect.v3.domain.Zone.Type.PRIMARY;
import javax.ws.rs.Consumes;
import javax.ws.rs.core.MediaType;
import org.jclouds.dynect.v3.domain.Zone;
import org.jclouds.dynect.v3.internal.BaseDynECTParseTest;
import org.jclouds.rest.annotations.SelectJson;
import org.testng.annotations.Test;
/**
* @author Adrian Cole
*/
@Test(groups = "unit")
public class GetZoneResponseTest extends BaseDynECTParseTest<Zone> {
@Override
public String resource() {
return "/get_zone.json";
}
@Override
@SelectJson("data")
@Consumes(MediaType.APPLICATION_JSON)
public Zone expected() {
return Zone.builder().type(PRIMARY).serialStyle(INCREMENT).serial(5).name("jclouds.org").build();
}
}

View File

@ -0,0 +1,57 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.dynect.v3.parse;
import static com.google.common.base.Functions.compose;
import org.jclouds.dynect.v3.functions.ExtractNames;
import org.jclouds.dynect.v3.internal.BaseDynECTParseTest;
import org.jclouds.http.HttpResponse;
import org.jclouds.rest.annotations.SelectJson;
import org.testng.annotations.Test;
import com.google.common.base.Function;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Injector;
/**
* @author Adrian Cole
*/
@Test(groups = "unit")
public class ListZonesResponseTest extends BaseDynECTParseTest<FluentIterable<String>> {
@Override
public String resource() {
return "/list_zones.json";
}
@Override
@SelectJson("data")
public FluentIterable<String> expected() {
return FluentIterable.from(ImmutableSet.of("0.0.0.0.d.6.e.0.0.a.2.ip6.arpa", "126.12.44.in-addr.arpa", "jclouds.org"));
}
// TODO: currently our parsing of annotations on expected() ignores @Transform
@Override
protected Function<HttpResponse, FluentIterable<String>> parser(Injector i) {
return compose(new ExtractNames(), super.parser(i));
}
}

View File

@ -0,0 +1 @@
{"status": "success", "data": {"zone_type": "Primary", "serial_style": "increment", "serial": 5, "zone": "jclouds.org"}, "job_id": 260737493, "msgs": [{"INFO": "get: Your zone, jclouds.org", "SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}

View File

@ -0,0 +1 @@
{"status": "success", "data": ["/REST/Zone/0.0.0.0.d.6.e.0.0.a.2.ip6.arpa/", "/REST/Zone/126.12.44.in-addr.arpa/", "/REST/Zone/jclouds.org/"], "job_id": 260657587, "msgs": [{"INFO": "get: Your 3 zones", "SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}