From 34d013f7db138bb438f8e993a0fa5e9e4557e259 Mon Sep 17 00:00:00 2001 From: Andrea Turli Date: Tue, 10 Jan 2017 11:11:18 +0100 Subject: [PATCH] add ApiMetadata and ProviderMetadata - add skeleton for PacketApi with ProjectApi only - add XAuthTokenToRequest filter - add HttpApiModule and ParserModule - add ProjectApi feature with Mock and Live Tests - add pagination to Project API - add test pagination --- .../java/org/jclouds/packet/PacketApi.java | 40 +++ .../org/jclouds/packet/PacketApiMetadata.java | 86 +++++ .../packet/PacketProviderMetadata.java | 78 +++++ .../config/PacketComputeParserModule.java | 30 ++ .../packet/config/PacketHttpApiModule.java | 55 +++ .../domain/internal/PaginatedCollection.java | 84 +++++ .../packet/domain/options/ListOptions.java | 60 ++++ .../jclouds/packet/features/ProjectApi.java | 93 ++++++ .../filters/AddXAuthTokenToRequest.java | 47 +++ .../packet/functions/BaseToPagedIterable.java | 59 ++++ .../packet/functions/LinkToListOptions.java | 63 ++++ .../packet/handlers/PacketErrorHandler.java | 64 ++++ .../packet/PacketProviderMetadataTest.java | 29 ++ .../internal/BasePacketApiLiveTest.java | 73 ++++ .../internal/BasePacketApiMockTest.java | 145 ++++++++ .../packet/features/ProjectApiLiveTest.java | 63 ++++ .../packet/features/ProjectApiMockTest.java | 79 +++++ .../functions/LinkToListOptionsTest.java | 57 ++++ .../src/test/resources/logback-test.xml | 42 +++ .../src/test/resources/projects-first.json | 315 ++++++++++++++++++ .../src/test/resources/projects-last.json | 197 +++++++++++ .../packet/src/test/resources/projects.json | 1 + 22 files changed, 1760 insertions(+) create mode 100644 providers/packet/src/main/java/org/jclouds/packet/PacketApi.java create mode 100644 providers/packet/src/main/java/org/jclouds/packet/PacketApiMetadata.java create mode 100644 providers/packet/src/main/java/org/jclouds/packet/PacketProviderMetadata.java create mode 100644 providers/packet/src/main/java/org/jclouds/packet/config/PacketComputeParserModule.java create mode 100644 providers/packet/src/main/java/org/jclouds/packet/config/PacketHttpApiModule.java create mode 100644 providers/packet/src/main/java/org/jclouds/packet/domain/internal/PaginatedCollection.java create mode 100644 providers/packet/src/main/java/org/jclouds/packet/domain/options/ListOptions.java create mode 100644 providers/packet/src/main/java/org/jclouds/packet/features/ProjectApi.java create mode 100644 providers/packet/src/main/java/org/jclouds/packet/filters/AddXAuthTokenToRequest.java create mode 100644 providers/packet/src/main/java/org/jclouds/packet/functions/BaseToPagedIterable.java create mode 100644 providers/packet/src/main/java/org/jclouds/packet/functions/LinkToListOptions.java create mode 100644 providers/packet/src/main/java/org/jclouds/packet/handlers/PacketErrorHandler.java create mode 100644 providers/packet/src/test/java/org/jclouds/packet/PacketProviderMetadataTest.java create mode 100644 providers/packet/src/test/java/org/jclouds/packet/compute/internal/BasePacketApiLiveTest.java create mode 100644 providers/packet/src/test/java/org/jclouds/packet/compute/internal/BasePacketApiMockTest.java create mode 100644 providers/packet/src/test/java/org/jclouds/packet/features/ProjectApiLiveTest.java create mode 100644 providers/packet/src/test/java/org/jclouds/packet/features/ProjectApiMockTest.java create mode 100644 providers/packet/src/test/java/org/jclouds/packet/functions/LinkToListOptionsTest.java create mode 100644 providers/packet/src/test/resources/logback-test.xml create mode 100644 providers/packet/src/test/resources/projects-first.json create mode 100644 providers/packet/src/test/resources/projects-last.json create mode 100644 providers/packet/src/test/resources/projects.json diff --git a/providers/packet/src/main/java/org/jclouds/packet/PacketApi.java b/providers/packet/src/main/java/org/jclouds/packet/PacketApi.java new file mode 100644 index 0000000000..1cb8e9b529 --- /dev/null +++ b/providers/packet/src/main/java/org/jclouds/packet/PacketApi.java @@ -0,0 +1,40 @@ +/* + * 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.packet; + +import java.io.Closeable; + +import org.jclouds.packet.features.ProjectApi; +import org.jclouds.rest.annotations.Delegate; + +/** + * The Packet API is a REST API for managing your services and deployments. + *

+ * + * @see doc + */ +public interface PacketApi extends Closeable { + + /** + * The Packet API includes operations for managing project. + * + * @see docs + */ + @Delegate + ProjectApi projectApi(); + +} diff --git a/providers/packet/src/main/java/org/jclouds/packet/PacketApiMetadata.java b/providers/packet/src/main/java/org/jclouds/packet/PacketApiMetadata.java new file mode 100644 index 0000000000..75ba0e6817 --- /dev/null +++ b/providers/packet/src/main/java/org/jclouds/packet/PacketApiMetadata.java @@ -0,0 +1,86 @@ +/* + * 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.packet; + +import java.net.URI; +import java.util.Properties; + +import org.jclouds.apis.ApiMetadata; +import org.jclouds.packet.config.PacketComputeParserModule; +import org.jclouds.packet.config.PacketHttpApiModule; +import org.jclouds.rest.internal.BaseHttpApiMetadata; + +import com.google.common.collect.ImmutableSet; +import com.google.inject.Module; + +import static org.jclouds.compute.config.ComputeServiceProperties.TEMPLATE; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING; + +/** + * Implementation of {@link ApiMetadata} for Packet API + */ +public class PacketApiMetadata extends BaseHttpApiMetadata { + + @Override + public Builder toBuilder() { + return new Builder().fromApiMetadata(this); + } + + public PacketApiMetadata() { + this(new Builder()); + } + + protected PacketApiMetadata(Builder builder) { + super(builder); + } + + public static Properties defaultProperties() { + Properties properties = BaseHttpApiMetadata.defaultProperties(); + properties.put(TEMPLATE, "osFamily=UBUNTU,os64Bit=true,osVersionMatches=16.*"); + properties.put(TIMEOUT_NODE_RUNNING, 300000); // 5 mins + return properties; + } + + public static class Builder extends BaseHttpApiMetadata.Builder { + + protected Builder() { + id("packet") + .name("Packet API") + .identityName("Packet Project Id") + .credentialName("Must be Packet Token") + .documentation(URI.create("https://www.packet.net/help/api/#")) + .defaultEndpoint("https://api.packet.net") + .defaultProperties(PacketApiMetadata.defaultProperties()) + //.view(typeToken(ComputeServiceContext.class)) + .defaultModules(ImmutableSet.>builder() + .add(PacketHttpApiModule.class) + .add(PacketComputeParserModule.class) + //.add(PacketComputeServiceContextModule.class) + .build()); + } + + @Override + public PacketApiMetadata build() { + return new PacketApiMetadata(this); + } + + @Override + protected Builder self() { + return this; + } + } +} diff --git a/providers/packet/src/main/java/org/jclouds/packet/PacketProviderMetadata.java b/providers/packet/src/main/java/org/jclouds/packet/PacketProviderMetadata.java new file mode 100644 index 0000000000..69e0918182 --- /dev/null +++ b/providers/packet/src/main/java/org/jclouds/packet/PacketProviderMetadata.java @@ -0,0 +1,78 @@ +/* + * 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.packet; + +import java.net.URI; +import java.util.Properties; + +import org.jclouds.providers.ProviderMetadata; +import org.jclouds.providers.internal.BaseProviderMetadata; + +import com.google.auto.service.AutoService; + +@AutoService(ProviderMetadata.class) +public class PacketProviderMetadata extends BaseProviderMetadata { + + public static Builder builder() { + return new Builder(); + } + + @Override + public Builder toBuilder() { + return builder().fromProviderMetadata(this); + } + + public PacketProviderMetadata() { + super(builder()); + } + + public PacketProviderMetadata(Builder builder) { + super(builder); + } + + public static Properties defaultProperties() { + final Properties properties = PacketApiMetadata.defaultProperties(); + return properties; + } + + public static class Builder extends BaseProviderMetadata.Builder { + + protected Builder() { + id("packet") + .name("Packet Compute Services") + .apiMetadata(new PacketApiMetadata()) + .homepage(URI.create("https://www.packet.net/")) + .console(URI.create("https://app.packet.net/portal")) + .endpoint("https://api.packet.net") + .iso3166Codes("US-CA", "US-NJ", "NL", "JP") + .defaultProperties(PacketProviderMetadata.defaultProperties()); + } + + @Override + public PacketProviderMetadata build() { + return new PacketProviderMetadata(this); + } + + @Override + public Builder fromProviderMetadata(ProviderMetadata in) { + super.fromProviderMetadata(in); + return this; + } + } +} + + diff --git a/providers/packet/src/main/java/org/jclouds/packet/config/PacketComputeParserModule.java b/providers/packet/src/main/java/org/jclouds/packet/config/PacketComputeParserModule.java new file mode 100644 index 0000000000..8471fc48d3 --- /dev/null +++ b/providers/packet/src/main/java/org/jclouds/packet/config/PacketComputeParserModule.java @@ -0,0 +1,30 @@ +/* + * 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.packet.config; + +import org.jclouds.json.config.GsonModule; + +import com.google.inject.AbstractModule; + +public class PacketComputeParserModule extends AbstractModule { + + @Override + protected void configure() { + bind(GsonModule.DateAdapter.class).to(GsonModule.Iso8601DateAdapter.class); + } + +} diff --git a/providers/packet/src/main/java/org/jclouds/packet/config/PacketHttpApiModule.java b/providers/packet/src/main/java/org/jclouds/packet/config/PacketHttpApiModule.java new file mode 100644 index 0000000000..e74bb19b34 --- /dev/null +++ b/providers/packet/src/main/java/org/jclouds/packet/config/PacketHttpApiModule.java @@ -0,0 +1,55 @@ +/* + * 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.packet.config; + +import org.jclouds.http.HttpErrorHandler; +import org.jclouds.http.annotation.ClientError; +import org.jclouds.http.annotation.Redirection; +import org.jclouds.http.annotation.ServerError; +import org.jclouds.location.suppliers.ImplicitLocationSupplier; +import org.jclouds.location.suppliers.implicit.FirstRegion; +import org.jclouds.packet.PacketApi; +import org.jclouds.packet.domain.Href; +import org.jclouds.packet.domain.options.ListOptions; +import org.jclouds.packet.functions.LinkToListOptions; +import org.jclouds.packet.handlers.PacketErrorHandler; +import org.jclouds.rest.ConfiguresHttpApi; +import org.jclouds.rest.config.HttpApiModule; + +import com.google.common.base.Function; +import com.google.inject.Scopes; +import com.google.inject.TypeLiteral; + +@ConfiguresHttpApi +public class PacketHttpApiModule extends HttpApiModule { + + @Override + protected void configure() { + super.configure(); + bind(ImplicitLocationSupplier.class).to(FirstRegion.class).in(Scopes.SINGLETON); + bind(new TypeLiteral>() { + }).to(LinkToListOptions.class); + } + + @Override + protected void bindErrorHandlers() { + bind(HttpErrorHandler.class).annotatedWith(Redirection.class).to(PacketErrorHandler.class); + bind(HttpErrorHandler.class).annotatedWith(ClientError.class).to(PacketErrorHandler.class); + bind(HttpErrorHandler.class).annotatedWith(ServerError.class).to(PacketErrorHandler.class); + } + +} diff --git a/providers/packet/src/main/java/org/jclouds/packet/domain/internal/PaginatedCollection.java b/providers/packet/src/main/java/org/jclouds/packet/domain/internal/PaginatedCollection.java new file mode 100644 index 0000000000..047151ec43 --- /dev/null +++ b/providers/packet/src/main/java/org/jclouds/packet/domain/internal/PaginatedCollection.java @@ -0,0 +1,84 @@ +/* + * 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.packet.domain.internal; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Iterator; +import java.util.List; + +import org.jclouds.collect.IterableWithMarker; +import org.jclouds.javax.annotation.Nullable; +import org.jclouds.json.SerializedNames; +import org.jclouds.packet.domain.Href; + +import com.google.auto.value.AutoValue; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableList; + +/** + * Base class for all collections that return paginated results. + */ +public abstract class PaginatedCollection extends IterableWithMarker { + + @AutoValue + public abstract static class Meta { + public abstract long total(); + @Nullable public abstract Href first(); + @Nullable public abstract Href previous(); + @Nullable public abstract Href self(); + @Nullable public abstract Href next(); + @Nullable public abstract Href last(); + + @SerializedNames({ "total", "first", "previous", "self", "next", "last" }) + public static Meta create(long total, Href first, Href previous, Href self, Href next, Href last) { + return new AutoValue_PaginatedCollection_Meta(total, first, previous, self, next, last); + } + + Meta() { } + } + + private final List items; + private final Meta meta; + + protected PaginatedCollection(List items, Meta meta) { + this.items = ImmutableList.copyOf(checkNotNull(items, "items cannot be null")); + this.meta = checkNotNull(meta, "meta cannot be null"); + } + + public List items() { + return items; + } + + public Meta meta() { + return meta; + } + + @Override + public Iterator iterator() { + return items.iterator(); + } + + @Override + public Optional nextMarker() { + if (meta.next() == null) { + return Optional.absent(); + } + return Optional.fromNullable((Object) meta.next()); + } + +} diff --git a/providers/packet/src/main/java/org/jclouds/packet/domain/options/ListOptions.java b/providers/packet/src/main/java/org/jclouds/packet/domain/options/ListOptions.java new file mode 100644 index 0000000000..c858a7f22d --- /dev/null +++ b/providers/packet/src/main/java/org/jclouds/packet/domain/options/ListOptions.java @@ -0,0 +1,60 @@ +/* + * 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.packet.domain.options; + +import org.jclouds.http.options.BaseHttpRequestOptions; + +/** + * Options to customize how paginated lists are returned. + */ +public class ListOptions extends BaseHttpRequestOptions { + public static final String PAGE_PARAM = "page"; + public static final String PER_PAGE_PARAM = "per_page"; + + /** + * Configures the number of entries to return in each page. + */ + public ListOptions perPage(int perPage) { + queryParameters.put(PER_PAGE_PARAM, String.valueOf(perPage)); + return this; + } + + /** + * Configures the number of the page to be returned. + */ + public ListOptions page(int page) { + queryParameters.put(PAGE_PARAM, String.valueOf(page)); + return this; + } + + public static final class Builder { + + /** + * @see {@link ListOptions#perPage(int)} + */ + public static ListOptions perPage(int perPage) { + return new ListOptions().perPage(perPage); + } + + /** + * @see {@link ListOptions#page(int)} + */ + public static ListOptions page(int page) { + return new ListOptions().page(page); + } + } +} diff --git a/providers/packet/src/main/java/org/jclouds/packet/features/ProjectApi.java b/providers/packet/src/main/java/org/jclouds/packet/features/ProjectApi.java new file mode 100644 index 0000000000..e6bf0ca7b0 --- /dev/null +++ b/providers/packet/src/main/java/org/jclouds/packet/features/ProjectApi.java @@ -0,0 +1,93 @@ +/* + * 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.packet.features; + +import java.beans.ConstructorProperties; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.core.MediaType; + +import org.jclouds.Fallbacks; +import org.jclouds.collect.IterableWithMarker; +import org.jclouds.collect.PagedIterable; +import org.jclouds.http.functions.ParseJson; +import org.jclouds.json.Json; +import org.jclouds.packet.PacketApi; +import org.jclouds.packet.domain.Href; +import org.jclouds.packet.domain.Project; +import org.jclouds.packet.domain.internal.PaginatedCollection; +import org.jclouds.packet.domain.options.ListOptions; +import org.jclouds.packet.filters.AddXAuthTokenToRequest; +import org.jclouds.packet.functions.BaseToPagedIterable; +import org.jclouds.rest.annotations.Fallback; +import org.jclouds.rest.annotations.RequestFilters; +import org.jclouds.rest.annotations.ResponseParser; +import org.jclouds.rest.annotations.Transform; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.inject.TypeLiteral; + +@Path("/projects") +@Consumes(MediaType.APPLICATION_JSON) +@RequestFilters(AddXAuthTokenToRequest.class) +public interface ProjectApi { + + + @Named("project:list") + @GET + @ResponseParser(ParseProjects.class) + @Transform(ParseProjects.ToPagedIterable.class) + @Fallback(Fallbacks.EmptyPagedIterableOnNotFoundOr404.class) + PagedIterable list(); + + @Named("project:list") + @GET + @ResponseParser(ParseProjects.class) + @Fallback(Fallbacks.EmptyIterableWithMarkerOnNotFoundOr404.class) + IterableWithMarker list(ListOptions options); + + final class ParseProjects extends ParseJson { + @Inject + ParseProjects(Json json) { + super(json, TypeLiteral.get(Projects.class)); + } + + private static class Projects extends PaginatedCollection { + @ConstructorProperties({ "projects", "meta" }) + public Projects(List items, Meta meta) { + super(items, meta); + } + } + + private static class ToPagedIterable extends BaseToPagedIterable { + @Inject ToPagedIterable(PacketApi api, Function linkToOptions) { + super(api, linkToOptions); + } + + @Override + protected IterableWithMarker fetchPageUsingOptions(ListOptions options, Optional arg0) { + return api.projectApi().list(options); + } + } + } +} diff --git a/providers/packet/src/main/java/org/jclouds/packet/filters/AddXAuthTokenToRequest.java b/providers/packet/src/main/java/org/jclouds/packet/filters/AddXAuthTokenToRequest.java new file mode 100644 index 0000000000..e9d6bddb62 --- /dev/null +++ b/providers/packet/src/main/java/org/jclouds/packet/filters/AddXAuthTokenToRequest.java @@ -0,0 +1,47 @@ +/* + * 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.packet.filters; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.jclouds.domain.Credentials; +import org.jclouds.http.HttpException; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpRequestFilter; +import org.jclouds.location.Provider; + +import com.google.common.base.Supplier; + +import static com.google.common.base.Preconditions.checkNotNull; + +@Singleton +public class AddXAuthTokenToRequest implements HttpRequestFilter { + + private final Supplier creds; + + @Inject + AddXAuthTokenToRequest(@Provider Supplier creds) { + this.creds = creds; + } + + @Override + public HttpRequest filter(HttpRequest request) throws HttpException { + Credentials currentCreds = checkNotNull(creds.get(), "credential supplier returned null"); + return request.toBuilder().replaceHeader("X-Auth-Token", currentCreds.credential).build(); + } +} diff --git a/providers/packet/src/main/java/org/jclouds/packet/functions/BaseToPagedIterable.java b/providers/packet/src/main/java/org/jclouds/packet/functions/BaseToPagedIterable.java new file mode 100644 index 0000000000..c5c275b56e --- /dev/null +++ b/providers/packet/src/main/java/org/jclouds/packet/functions/BaseToPagedIterable.java @@ -0,0 +1,59 @@ +/* + * 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.packet.functions; + +import javax.inject.Inject; + +import org.jclouds.collect.IterableWithMarker; +import org.jclouds.collect.internal.Arg0ToPagedIterable; +import org.jclouds.packet.PacketApi; +import org.jclouds.packet.domain.Href; +import org.jclouds.packet.domain.options.ListOptions; + +import com.google.common.base.Function; +import com.google.common.base.Optional; + +/** + * Base class to implement the functions that build the + * PagedIterable. Subclasses just need to override the + * {@link #fetchPageUsingOptions(ListOptions, Optional)} to invoke the right API + * method with the given options parameter to get the next page. + */ +public abstract class BaseToPagedIterable extends + Arg0ToPagedIterable> { + private final Function linkToOptions; + protected final PacketApi api; + + @Inject protected BaseToPagedIterable(PacketApi api, Function linkToOptions) { + this.api = api; + this.linkToOptions = linkToOptions; + } + + protected abstract IterableWithMarker fetchPageUsingOptions(O options, Optional arg0); + + @Override + protected Function> markerToNextForArg0(final Optional arg0) { + return new Function>() { + @Override + public IterableWithMarker apply(Object input) { + O nextOptions = linkToOptions.apply(Href.class.cast(input)); + return fetchPageUsingOptions(nextOptions, arg0); + } + }; + } + +} diff --git a/providers/packet/src/main/java/org/jclouds/packet/functions/LinkToListOptions.java b/providers/packet/src/main/java/org/jclouds/packet/functions/LinkToListOptions.java new file mode 100644 index 0000000000..4aef811000 --- /dev/null +++ b/providers/packet/src/main/java/org/jclouds/packet/functions/LinkToListOptions.java @@ -0,0 +1,63 @@ +/* + * 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.packet.functions; + +import java.net.URI; + +import org.jclouds.packet.domain.Href; +import org.jclouds.packet.domain.options.ListOptions; + +import com.google.common.base.Function; +import com.google.common.collect.Multimap; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Strings.emptyToNull; +import static com.google.common.collect.Iterables.getFirst; +import static org.jclouds.http.utils.Queries.queryParser; +import static org.jclouds.packet.domain.options.ListOptions.PAGE_PARAM; +import static org.jclouds.packet.domain.options.ListOptions.PER_PAGE_PARAM; + +/** + * Transforms an href returned by the API into a {@link ListOptions} that can be + * used to perform a request to get another page of a paginated list. + */ +public class LinkToListOptions implements Function { + + @Override + public ListOptions apply(Href input) { + checkNotNull(input, "input cannot be null"); + + Multimap queryParams = queryParser().apply(URI.create(input.href()).getQuery()); + String nextPage = getFirstOrNull(PAGE_PARAM, queryParams); + String nextPerPage = getFirstOrNull(PER_PAGE_PARAM, queryParams); + + ListOptions options = new ListOptions(); + if (nextPage != null) { + options.page(Integer.parseInt(nextPage)); + } + if (nextPerPage != null) { + options.perPage(Integer.parseInt(nextPerPage)); + } + + return options; + } + + public static String getFirstOrNull(String key, Multimap params) { + return params.containsKey(key) ? emptyToNull(getFirst(params.get(key), null)) : null; + } + +} diff --git a/providers/packet/src/main/java/org/jclouds/packet/handlers/PacketErrorHandler.java b/providers/packet/src/main/java/org/jclouds/packet/handlers/PacketErrorHandler.java new file mode 100644 index 0000000000..01d870b1ca --- /dev/null +++ b/providers/packet/src/main/java/org/jclouds/packet/handlers/PacketErrorHandler.java @@ -0,0 +1,64 @@ +/* + * 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.packet.handlers; + +import javax.inject.Singleton; + +import org.jclouds.http.HttpCommand; +import org.jclouds.http.HttpErrorHandler; +import org.jclouds.http.HttpResponse; +import org.jclouds.http.HttpResponseException; +import org.jclouds.rest.AuthorizationException; +import org.jclouds.rest.ResourceNotFoundException; + +import static org.jclouds.http.HttpUtils.closeClientButKeepContentStream; + +/** + * This will parse and set an appropriate exception on the command object. + */ +@Singleton +public class PacketErrorHandler implements HttpErrorHandler { + + public void handleError(HttpCommand command, HttpResponse response) { + // it is important to always read fully and close streams + byte[] data = closeClientButKeepContentStream(response); + String message = data != null ? new String(data) : null; + + Exception exception = message != null ? new HttpResponseException(command, response, message) + : new HttpResponseException(command, response); + message = message != null ? message : String.format("%s -> %s", command.getCurrentRequest().getRequestLine(), + response.getStatusLine()); + switch (response.getStatusCode()) { + case 400: + exception = new IllegalArgumentException(message, exception); + break; + case 401: + case 403: + exception = new AuthorizationException(message, exception); + break; + case 404: + if (!command.getCurrentRequest().getMethod().equals("DELETE")) { + exception = new ResourceNotFoundException(message, exception); + } + break; + case 409: + exception = new IllegalStateException(message, exception); + break; + } + command.setException(exception); + } +} diff --git a/providers/packet/src/test/java/org/jclouds/packet/PacketProviderMetadataTest.java b/providers/packet/src/test/java/org/jclouds/packet/PacketProviderMetadataTest.java new file mode 100644 index 0000000000..cc0c8c51a3 --- /dev/null +++ b/providers/packet/src/test/java/org/jclouds/packet/PacketProviderMetadataTest.java @@ -0,0 +1,29 @@ +/* + * 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.packet; + +import org.jclouds.providers.internal.BaseProviderMetadataTest; +import org.testng.annotations.Test; + +@Test(groups = "unit", testName = "PacketProviderMetadataTest") +public class PacketProviderMetadataTest extends BaseProviderMetadataTest { + + public PacketProviderMetadataTest() { + super(new PacketProviderMetadata(), new PacketApiMetadata()); + } + +} diff --git a/providers/packet/src/test/java/org/jclouds/packet/compute/internal/BasePacketApiLiveTest.java b/providers/packet/src/test/java/org/jclouds/packet/compute/internal/BasePacketApiLiveTest.java new file mode 100644 index 0000000000..6c8cf63609 --- /dev/null +++ b/providers/packet/src/test/java/org/jclouds/packet/compute/internal/BasePacketApiLiveTest.java @@ -0,0 +1,73 @@ +/* + * 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.packet.compute.internal; + +import java.util.Properties; +import java.util.concurrent.TimeUnit; + +import org.jclouds.apis.BaseApiLiveTest; +import org.jclouds.compute.config.ComputeServiceProperties; +import org.jclouds.packet.PacketApi; + +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 static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING; +import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_TERMINATED; +import static org.testng.Assert.assertTrue; + +public class BasePacketApiLiveTest extends BaseApiLiveTest { + + private Predicate deviceRunning; + private Predicate deviceTerminated; + + public BasePacketApiLiveTest() { + provider = "packet"; + } + + @Override + protected Properties setupProperties() { + Properties props = super.setupProperties(); + props.put(ComputeServiceProperties.POLL_INITIAL_PERIOD, 1000); + props.put(ComputeServiceProperties.POLL_MAX_PERIOD, 10000); + props.put(ComputeServiceProperties.TIMEOUT_IMAGE_AVAILABLE, TimeUnit.MINUTES.toMillis(45)); + return props; + } + + @Override + protected PacketApi create(Properties props, Iterable modules) { + Injector injector = newBuilder().modules(modules).overrides(props).buildInjector(); + deviceRunning = injector.getInstance(Key.get(new TypeLiteral>(){}, + Names.named(TIMEOUT_NODE_RUNNING))); + deviceTerminated = injector.getInstance(Key.get(new TypeLiteral>(){}, + Names.named(TIMEOUT_NODE_TERMINATED))); + return injector.getInstance(PacketApi.class); + } + + protected void assertNodeRunning(String deviceId) { + assertTrue(deviceRunning.apply(deviceId), String.format("Device %s did not start in the configured timeout", deviceId)); + } + + protected void assertNodeTerminated(String deviceId) { + assertTrue(deviceTerminated.apply(deviceId), String.format("Device %s was not terminated in the configured timeout", deviceId)); + } + +} diff --git a/providers/packet/src/test/java/org/jclouds/packet/compute/internal/BasePacketApiMockTest.java b/providers/packet/src/test/java/org/jclouds/packet/compute/internal/BasePacketApiMockTest.java new file mode 100644 index 0000000000..5b8d6aebf1 --- /dev/null +++ b/providers/packet/src/test/java/org/jclouds/packet/compute/internal/BasePacketApiMockTest.java @@ -0,0 +1,145 @@ +/* + * 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.packet.compute.internal; + +import java.io.IOException; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import org.jclouds.ContextBuilder; +import org.jclouds.concurrent.config.ExecutorServiceModule; +import org.jclouds.json.Json; +import org.jclouds.packet.PacketApi; +import org.jclouds.packet.PacketProviderMetadata; +import org.jclouds.rest.ApiContext; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; + +import com.google.common.base.Charsets; +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableSet; +import com.google.common.io.Resources; +import com.google.common.reflect.TypeToken; +import com.google.gson.JsonParser; +import com.google.inject.Module; +import com.squareup.okhttp.mockwebserver.MockResponse; +import com.squareup.okhttp.mockwebserver.MockWebServer; +import com.squareup.okhttp.mockwebserver.RecordedRequest; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor; +import static org.jclouds.Constants.PROPERTY_MAX_RETRIES; +import static org.testng.Assert.assertEquals; + +public class BasePacketApiMockTest { + + private static final String X_AUTHORIZATION_TOKEN = "c5401990f0c24135e8d6b5d260603fc71696d4738da9aa04a720229a01a2521d"; + private static final String DEFAULT_ENDPOINT = new PacketProviderMetadata().getEndpoint(); + + private final Set modules = ImmutableSet. of(new ExecutorServiceModule(sameThreadExecutor())); + + protected MockWebServer server; + protected PacketApi api; + private Json json; + + // So that we can ignore formatting. + private final JsonParser parser = new JsonParser(); + + @BeforeMethod + public void start() throws IOException { + server = new MockWebServer(); + server.play(); + ApiContext ctx = ContextBuilder.newBuilder("packet") + .credentials("", X_AUTHORIZATION_TOKEN) + .endpoint(url("")) + .modules(modules) + .overrides(overrides()) + .build(); + json = ctx.utils().injector().getInstance(Json.class); + api = ctx.getApi(); + } + + @AfterMethod(alwaysRun = true) + public void stop() throws IOException { + server.shutdown(); + api.close(); + } + + protected Properties overrides() { + Properties properties = new Properties(); + properties.put(PROPERTY_MAX_RETRIES, "0"); // Do not retry + return properties; + } + + protected String url(String path) { + return server.getUrl(path).toString(); + } + + protected MockResponse jsonResponse(String resource) { + return new MockResponse().addHeader("Content-Type", "application/json").setBody(stringFromResource(resource)); + } + + protected MockResponse response404() { + return new MockResponse().setStatus("HTTP/1.1 404 Not Found"); + } + + protected MockResponse response204() { + return new MockResponse().setStatus("HTTP/1.1 204 No Content"); + } + + protected String stringFromResource(String resourceName) { + try { + return Resources.toString(getClass().getResource(resourceName), Charsets.UTF_8) + .replace(DEFAULT_ENDPOINT, url("")); + } catch (IOException e) { + throw Throwables.propagate(e); + } + } + + protected T onlyObjectFromResource(String resourceName, TypeToken> type) { + // Assume JSON objects passed here will be in the form: { "entity": { ... } } + String text = stringFromResource(resourceName); + Map object = json.fromJson(text, type.getType()); + checkArgument(!object.isEmpty(), "The given json does not contain any object: %s", text); + checkArgument(object.keySet().size() == 1, "The given json does not contain more than one object: %s", text); + return object.get(getOnlyElement(object.keySet())); + } + + protected T objectFromResource(String resourceName, Class type) { + String text = stringFromResource(resourceName); + return json.fromJson(text, type); + } + + protected RecordedRequest assertSent(MockWebServer server, String method, String path) throws InterruptedException { + RecordedRequest request = server.takeRequest(); + assertEquals(request.getMethod(), method); + assertEquals(request.getPath(), path); + assertEquals(request.getHeader("Accept"), "application/json"); + assertEquals(request.getHeader("X-Auth-Token"), X_AUTHORIZATION_TOKEN); + return request; + } + + protected RecordedRequest assertSent(MockWebServer server, String method, String path, String json) + throws InterruptedException { + RecordedRequest request = assertSent(server, method, path); + assertEquals(request.getHeader("Content-Type"), "application/json"); + assertEquals(parser.parse(new String(request.getBody(), Charsets.UTF_8)), parser.parse(json)); + return request; + } +} diff --git a/providers/packet/src/test/java/org/jclouds/packet/features/ProjectApiLiveTest.java b/providers/packet/src/test/java/org/jclouds/packet/features/ProjectApiLiveTest.java new file mode 100644 index 0000000000..133e5ef910 --- /dev/null +++ b/providers/packet/src/test/java/org/jclouds/packet/features/ProjectApiLiveTest.java @@ -0,0 +1,63 @@ +/* + * 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.packet.features; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.jclouds.packet.compute.internal.BasePacketApiLiveTest; +import org.jclouds.packet.domain.Project; +import org.testng.annotations.Test; + +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; + +import static org.jclouds.packet.domain.options.ListOptions.Builder.page; +import static org.testng.Assert.assertTrue; +import static org.testng.util.Strings.isNullOrEmpty; + +@Test(groups = "live", testName = "ProjectApiLiveTest") +public class ProjectApiLiveTest extends BasePacketApiLiveTest { + + public void testListProjects() { + final AtomicInteger found = new AtomicInteger(0); + assertTrue(Iterables.all(api().list().concat(), new Predicate() { + @Override + public boolean apply(Project input) { + found.incrementAndGet(); + return !isNullOrEmpty(input.id()); + } + }), "All projects must have the 'id' field populated"); + assertTrue(found.get() > 0, "Expected some projects to be returned"); + } + + public void testListActionsOnePage() { + final AtomicInteger found = new AtomicInteger(0); + assertTrue(api().list(page(1).perPage(5)).allMatch(new Predicate() { + @Override + public boolean apply(Project input) { + found.incrementAndGet(); + return !isNullOrEmpty(input.id()); + } + }), "All projects must have the 'id' field populated"); + assertTrue(found.get() > 0, "Expected some projects to be returned"); + } + + + private ProjectApi api() { + return api.projectApi(); + } +} diff --git a/providers/packet/src/test/java/org/jclouds/packet/features/ProjectApiMockTest.java b/providers/packet/src/test/java/org/jclouds/packet/features/ProjectApiMockTest.java new file mode 100644 index 0000000000..28990209dc --- /dev/null +++ b/providers/packet/src/test/java/org/jclouds/packet/features/ProjectApiMockTest.java @@ -0,0 +1,79 @@ +/* + * 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.packet.features; + +import org.jclouds.packet.compute.internal.BasePacketApiMockTest; +import org.jclouds.packet.domain.Project; +import org.testng.annotations.Test; + +import static com.google.common.collect.Iterables.isEmpty; +import static com.google.common.collect.Iterables.size; +import static org.jclouds.packet.domain.options.ListOptions.Builder.page; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +@Test(groups = "unit", testName = "ProjectApiMockTest", singleThreaded = true) +public class ProjectApiMockTest extends BasePacketApiMockTest { + + public void testListProjects() throws InterruptedException { + server.enqueue(jsonResponse("/projects-first.json")); + server.enqueue(jsonResponse("/projects-last.json")); + + Iterable projects = api.projectApi().list().concat(); + + assertEquals(size(projects), 8); // Force the PagedIterable to advance + assertEquals(server.getRequestCount(), 2); + + assertSent(server, "GET", "/projects"); + assertSent(server, "GET", "/projects?page=2"); + } + + public void testListProjectsReturns404() throws InterruptedException { + server.enqueue(response404()); + + Iterable projects = api.projectApi().list().concat(); + + assertTrue(isEmpty(projects)); + + assertEquals(server.getRequestCount(), 1); + assertSent(server, "GET", "/projects"); + } + + public void testListProjectsWithOptions() throws InterruptedException { + server.enqueue(jsonResponse("/projects-first.json")); + + Iterable actions = api.projectApi().list(page(1).perPage(5)); + + assertEquals(size(actions), 5); + assertEquals(server.getRequestCount(), 1); + + assertSent(server, "GET", "/projects?page=1&per_page=5"); + } + + public void testListProjectsWithOptionsReturns404() throws InterruptedException { + server.enqueue(response404()); + + Iterable actions = api.projectApi().list(page(1).perPage(5)); + + assertTrue(isEmpty(actions)); + + assertEquals(server.getRequestCount(), 1); + assertSent(server, "GET", "/projects?page=1&per_page=5"); + } + + +} diff --git a/providers/packet/src/test/java/org/jclouds/packet/functions/LinkToListOptionsTest.java b/providers/packet/src/test/java/org/jclouds/packet/functions/LinkToListOptionsTest.java new file mode 100644 index 0000000000..15262e8d50 --- /dev/null +++ b/providers/packet/src/test/java/org/jclouds/packet/functions/LinkToListOptionsTest.java @@ -0,0 +1,57 @@ +/* + * 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.packet.functions; + +import org.jclouds.packet.domain.Href; +import org.jclouds.packet.domain.options.ListOptions; +import org.testng.annotations.Test; + +import com.google.common.collect.Multimap; + +import static com.google.common.collect.Iterables.getOnlyElement; +import static org.jclouds.packet.domain.options.ListOptions.PAGE_PARAM; +import static org.jclouds.packet.domain.options.ListOptions.PER_PAGE_PARAM; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; + +@Test(groups = "unit", testName = "LinkToListOptionsTest") +public class LinkToListOptionsTest { + + public void testNoOptions() { + LinkToListOptions function = new LinkToListOptions(); + + ListOptions options = function.apply(Href.create("https://api.packet.net/projects")); + assertNotNull(options); + + Multimap params = options.buildQueryParameters(); + assertFalse(params.containsKey(PAGE_PARAM)); + assertFalse(params.containsKey(PER_PAGE_PARAM)); + } + + public void testWithOptions() { + LinkToListOptions function = new LinkToListOptions(); + + ListOptions options = function.apply(Href.create("https://api.packet.net/projects?page=2&per_page=5")); + assertNotNull(options); + + Multimap params = options.buildQueryParameters(); + assertEquals(getOnlyElement(params.get(PAGE_PARAM)), "2"); + assertEquals(getOnlyElement(params.get(PER_PAGE_PARAM)), "5"); + } + +} diff --git a/providers/packet/src/test/resources/logback-test.xml b/providers/packet/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..cb55d49823 --- /dev/null +++ b/providers/packet/src/test/resources/logback-test.xml @@ -0,0 +1,42 @@ + + + + target/test-data/jclouds.log + + %d %-5p [%c] [%thread] %m%n + + + + target/test-data/jclouds-wire.log + + %d %-5p [%c] [%thread] %m%n + + + + target/jclouds-compute.log + + %d %-5p [%c] [%thread] %m%n + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/providers/packet/src/test/resources/projects-first.json b/providers/packet/src/test/resources/projects-first.json new file mode 100644 index 0000000000..f65d8e3ceb --- /dev/null +++ b/providers/packet/src/test/resources/projects-first.json @@ -0,0 +1,315 @@ +{ + "projects": [ + { + "id": "93907f48-adfe-43ed-ad89-0e6e83721a54", + "name": "Cloudsoft CCS Testing", + "created_at": "2016-09-15T08:50:58Z", + "updated_at": "2017-01-05T09:36:53Z", + "max_devices": { + "baremetal_0": null, + "baremetal_1": null, + "baremetal_2": null, + "baremetal_3": null, + "baremetal_2a": null, + "storage_1": null, + "storage_2": null + }, + "members": [ + { + "href": "/users/1140617d-262d-4502-a3d6-771d83c930da" + }, + { + "href": "/users/343345fe-18b3-46a3-9b9c-e4a2fe88ccbd" + }, + { + "href": "/users/73b0442e-cc4b-42a0-8d3a-c8dfb8a4ff2e" + }, + { + "href": "/users/ad711bc3-6333-449a-a405-23ca81f38c00" + } + ], + "memberships": [ + { + "href": "/memberships/914facae-547f-46fc-93e8-860eb53d9bf6" + }, + { + "href": "/memberships/1ce7c9d9-d11f-47f2-b5a6-d1221338ad69" + }, + { + "href": "/memberships/e25c4478-9e44-465a-a9d7-bffc7a83300d" + }, + { + "href": "/memberships/8a00c05b-3ddc-41c3-8dd7-4a3d5984ecec" + } + ], + "invitations": [], + "payment_method": { + "href": "/payment-methods/b2bb4ee0-506b-4c01-b17d-ba41d5c430c5" + }, + "devices": [ + { + "href": "/devices/4942a6c7-55c6-42cc-a06b-cccd6f2fa5df" + } + ], + "ssh_keys": [ + { + "href": "/ssh-keys/070e3282-5b6a-4f75-8f18-a4e7488eafaa" + } + ], + "volumes": [], + "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54" + }, + { + "id": "93907f48-adfe-43ed-ad89-0e6e83721a53", + "name": "Cloudsoft CCS Testing", + "created_at": "2016-09-15T08:50:58Z", + "updated_at": "2017-01-05T09:36:53Z", + "max_devices": { + "baremetal_0": null, + "baremetal_1": null, + "baremetal_2": null, + "baremetal_3": null, + "baremetal_2a": null, + "storage_1": null, + "storage_2": null + }, + "members": [ + { + "href": "/users/1140617d-262d-4502-a3d6-771d83c930da" + }, + { + "href": "/users/343345fe-18b3-46a3-9b9c-e4a2fe88ccbd" + }, + { + "href": "/users/73b0442e-cc4b-42a0-8d3a-c8dfb8a4ff2e" + }, + { + "href": "/users/ad711bc3-6333-449a-a405-23ca81f38c00" + } + ], + "memberships": [ + { + "href": "/memberships/914facae-547f-46fc-93e8-860eb53d9bf6" + }, + { + "href": "/memberships/1ce7c9d9-d11f-47f2-b5a6-d1221338ad69" + }, + { + "href": "/memberships/e25c4478-9e44-465a-a9d7-bffc7a83300d" + }, + { + "href": "/memberships/8a00c05b-3ddc-41c3-8dd7-4a3d5984ecec" + } + ], + "invitations": [], + "payment_method": { + "href": "/payment-methods/b2bb4ee0-506b-4c01-b17d-ba41d5c430c5" + }, + "devices": [ + { + "href": "/devices/4942a6c7-55c6-42cc-a06b-cccd6f2fa5df" + } + ], + "ssh_keys": [ + { + "href": "/ssh-keys/070e3282-5b6a-4f75-8f18-a4e7488eafaa" + } + ], + "volumes": [], + "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54" + }, + { + "id": "93907f48-adfe-43ed-ad89-0e6e83721a52", + "name": "Cloudsoft CCS Testing", + "created_at": "2016-09-15T08:50:58Z", + "updated_at": "2017-01-05T09:36:53Z", + "max_devices": { + "baremetal_0": null, + "baremetal_1": null, + "baremetal_2": null, + "baremetal_3": null, + "baremetal_2a": null, + "storage_1": null, + "storage_2": null + }, + "members": [ + { + "href": "/users/1140617d-262d-4502-a3d6-771d83c930da" + }, + { + "href": "/users/343345fe-18b3-46a3-9b9c-e4a2fe88ccbd" + }, + { + "href": "/users/73b0442e-cc4b-42a0-8d3a-c8dfb8a4ff2e" + }, + { + "href": "/users/ad711bc3-6333-449a-a405-23ca81f38c00" + } + ], + "memberships": [ + { + "href": "/memberships/914facae-547f-46fc-93e8-860eb53d9bf6" + }, + { + "href": "/memberships/1ce7c9d9-d11f-47f2-b5a6-d1221338ad69" + }, + { + "href": "/memberships/e25c4478-9e44-465a-a9d7-bffc7a83300d" + }, + { + "href": "/memberships/8a00c05b-3ddc-41c3-8dd7-4a3d5984ecec" + } + ], + "invitations": [], + "payment_method": { + "href": "/payment-methods/b2bb4ee0-506b-4c01-b17d-ba41d5c430c5" + }, + "devices": [ + { + "href": "/devices/4942a6c7-55c6-42cc-a06b-cccd6f2fa5df" + } + ], + "ssh_keys": [ + { + "href": "/ssh-keys/070e3282-5b6a-4f75-8f18-a4e7488eafaa" + } + ], + "volumes": [], + "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54" + }, + { + "id": "93907f48-adfe-43ed-ad89-0e6e83721a51", + "name": "Cloudsoft CCS Testing", + "created_at": "2016-09-15T08:50:58Z", + "updated_at": "2017-01-05T09:36:53Z", + "max_devices": { + "baremetal_0": null, + "baremetal_1": null, + "baremetal_2": null, + "baremetal_3": null, + "baremetal_2a": null, + "storage_1": null, + "storage_2": null + }, + "members": [ + { + "href": "/users/1140617d-262d-4502-a3d6-771d83c930da" + }, + { + "href": "/users/343345fe-18b3-46a3-9b9c-e4a2fe88ccbd" + }, + { + "href": "/users/73b0442e-cc4b-42a0-8d3a-c8dfb8a4ff2e" + }, + { + "href": "/users/ad711bc3-6333-449a-a405-23ca81f38c00" + } + ], + "memberships": [ + { + "href": "/memberships/914facae-547f-46fc-93e8-860eb53d9bf6" + }, + { + "href": "/memberships/1ce7c9d9-d11f-47f2-b5a6-d1221338ad69" + }, + { + "href": "/memberships/e25c4478-9e44-465a-a9d7-bffc7a83300d" + }, + { + "href": "/memberships/8a00c05b-3ddc-41c3-8dd7-4a3d5984ecec" + } + ], + "invitations": [], + "payment_method": { + "href": "/payment-methods/b2bb4ee0-506b-4c01-b17d-ba41d5c430c5" + }, + "devices": [ + { + "href": "/devices/4942a6c7-55c6-42cc-a06b-cccd6f2fa5df" + } + ], + "ssh_keys": [ + { + "href": "/ssh-keys/070e3282-5b6a-4f75-8f18-a4e7488eafaa" + } + ], + "volumes": [], + "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54" + }, + { + "id": "93907f48-adfe-43ed-ad89-0e6e83721a50", + "name": "Cloudsoft CCS Testing", + "created_at": "2016-09-15T08:50:58Z", + "updated_at": "2017-01-05T09:36:53Z", + "max_devices": { + "baremetal_0": null, + "baremetal_1": null, + "baremetal_2": null, + "baremetal_3": null, + "baremetal_2a": null, + "storage_1": null, + "storage_2": null + }, + "members": [ + { + "href": "/users/1140617d-262d-4502-a3d6-771d83c930da" + }, + { + "href": "/users/343345fe-18b3-46a3-9b9c-e4a2fe88ccbd" + }, + { + "href": "/users/73b0442e-cc4b-42a0-8d3a-c8dfb8a4ff2e" + }, + { + "href": "/users/ad711bc3-6333-449a-a405-23ca81f38c00" + } + ], + "memberships": [ + { + "href": "/memberships/914facae-547f-46fc-93e8-860eb53d9bf6" + }, + { + "href": "/memberships/1ce7c9d9-d11f-47f2-b5a6-d1221338ad69" + }, + { + "href": "/memberships/e25c4478-9e44-465a-a9d7-bffc7a83300d" + }, + { + "href": "/memberships/8a00c05b-3ddc-41c3-8dd7-4a3d5984ecec" + } + ], + "invitations": [], + "payment_method": { + "href": "/payment-methods/b2bb4ee0-506b-4c01-b17d-ba41d5c430c5" + }, + "devices": [ + { + "href": "/devices/4942a6c7-55c6-42cc-a06b-cccd6f2fa5df" + } + ], + "ssh_keys": [ + { + "href": "/ssh-keys/070e3282-5b6a-4f75-8f18-a4e7488eafaa" + } + ], + "volumes": [], + "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54" + } + ], + "meta": { + "first": { + "href": "/projects?page=1" + }, + "previous": null, + "self": { + "href": "/projects?page=1" + }, + "next": { + "href": "/projects?page=2" + }, + "last": { + "href": "/projects?page=2" + }, + "total": 8 + } +} diff --git a/providers/packet/src/test/resources/projects-last.json b/providers/packet/src/test/resources/projects-last.json new file mode 100644 index 0000000000..3d44e671f9 --- /dev/null +++ b/providers/packet/src/test/resources/projects-last.json @@ -0,0 +1,197 @@ +{ + "projects": [ + { + "id": "93907f48-adfe-43ed-ad89-0e6e83721a55", + "name": "Cloudsoft CCS Testing", + "created_at": "2016-09-15T08:50:58Z", + "updated_at": "2017-01-05T09:36:53Z", + "max_devices": { + "baremetal_0": null, + "baremetal_1": null, + "baremetal_2": null, + "baremetal_3": null, + "baremetal_2a": null, + "storage_1": null, + "storage_2": null + }, + "members": [ + { + "href": "/users/1140617d-262d-4502-a3d6-771d83c930da" + }, + { + "href": "/users/343345fe-18b3-46a3-9b9c-e4a2fe88ccbd" + }, + { + "href": "/users/73b0442e-cc4b-42a0-8d3a-c8dfb8a4ff2e" + }, + { + "href": "/users/ad711bc3-6333-449a-a405-23ca81f38c00" + } + ], + "memberships": [ + { + "href": "/memberships/914facae-547f-46fc-93e8-860eb53d9bf6" + }, + { + "href": "/memberships/1ce7c9d9-d11f-47f2-b5a6-d1221338ad69" + }, + { + "href": "/memberships/e25c4478-9e44-465a-a9d7-bffc7a83300d" + }, + { + "href": "/memberships/8a00c05b-3ddc-41c3-8dd7-4a3d5984ecec" + } + ], + "invitations": [], + "payment_method": { + "href": "/payment-methods/b2bb4ee0-506b-4c01-b17d-ba41d5c430c5" + }, + "devices": [ + { + "href": "/devices/4942a6c7-55c6-42cc-a06b-cccd6f2fa5df" + } + ], + "ssh_keys": [ + { + "href": "/ssh-keys/070e3282-5b6a-4f75-8f18-a4e7488eafaa" + } + ], + "volumes": [], + "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54" + }, + { + "id": "93907f48-adfe-43ed-ad89-0e6e83721a56", + "name": "Cloudsoft CCS Testing", + "created_at": "2016-09-15T08:50:58Z", + "updated_at": "2017-01-05T09:36:53Z", + "max_devices": { + "baremetal_0": null, + "baremetal_1": null, + "baremetal_2": null, + "baremetal_3": null, + "baremetal_2a": null, + "storage_1": null, + "storage_2": null + }, + "members": [ + { + "href": "/users/1140617d-262d-4502-a3d6-771d83c930da" + }, + { + "href": "/users/343345fe-18b3-46a3-9b9c-e4a2fe88ccbd" + }, + { + "href": "/users/73b0442e-cc4b-42a0-8d3a-c8dfb8a4ff2e" + }, + { + "href": "/users/ad711bc3-6333-449a-a405-23ca81f38c00" + } + ], + "memberships": [ + { + "href": "/memberships/914facae-547f-46fc-93e8-860eb53d9bf6" + }, + { + "href": "/memberships/1ce7c9d9-d11f-47f2-b5a6-d1221338ad69" + }, + { + "href": "/memberships/e25c4478-9e44-465a-a9d7-bffc7a83300d" + }, + { + "href": "/memberships/8a00c05b-3ddc-41c3-8dd7-4a3d5984ecec" + } + ], + "invitations": [], + "payment_method": { + "href": "/payment-methods/b2bb4ee0-506b-4c01-b17d-ba41d5c430c5" + }, + "devices": [ + { + "href": "/devices/4942a6c7-55c6-42cc-a06b-cccd6f2fa5df" + } + ], + "ssh_keys": [ + { + "href": "/ssh-keys/070e3282-5b6a-4f75-8f18-a4e7488eafaa" + } + ], + "volumes": [], + "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54" + }, + { + "id": "93907f48-adfe-43ed-ad89-0e6e83721a57", + "name": "Cloudsoft CCS Testing", + "created_at": "2016-09-15T08:50:58Z", + "updated_at": "2017-01-05T09:36:53Z", + "max_devices": { + "baremetal_0": null, + "baremetal_1": null, + "baremetal_2": null, + "baremetal_3": null, + "baremetal_2a": null, + "storage_1": null, + "storage_2": null + }, + "members": [ + { + "href": "/users/1140617d-262d-4502-a3d6-771d83c930da" + }, + { + "href": "/users/343345fe-18b3-46a3-9b9c-e4a2fe88ccbd" + }, + { + "href": "/users/73b0442e-cc4b-42a0-8d3a-c8dfb8a4ff2e" + }, + { + "href": "/users/ad711bc3-6333-449a-a405-23ca81f38c00" + } + ], + "memberships": [ + { + "href": "/memberships/914facae-547f-46fc-93e8-860eb53d9bf6" + }, + { + "href": "/memberships/1ce7c9d9-d11f-47f2-b5a6-d1221338ad69" + }, + { + "href": "/memberships/e25c4478-9e44-465a-a9d7-bffc7a83300d" + }, + { + "href": "/memberships/8a00c05b-3ddc-41c3-8dd7-4a3d5984ecec" + } + ], + "invitations": [], + "payment_method": { + "href": "/payment-methods/b2bb4ee0-506b-4c01-b17d-ba41d5c430c5" + }, + "devices": [ + { + "href": "/devices/4942a6c7-55c6-42cc-a06b-cccd6f2fa5df" + } + ], + "ssh_keys": [ + { + "href": "/ssh-keys/070e3282-5b6a-4f75-8f18-a4e7488eafaa" + } + ], + "volumes": [], + "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54" + } + ], + "meta": { + "first": { + "href": "/projects?page=1" + }, + "previous": { + "href": "/projects?page=1" + }, + "self": { + "href": "/projects?page=2" + }, + "next": null, + "last": { + "href": "/projects?page=2" + }, + "total": 8 + } +} diff --git a/providers/packet/src/test/resources/projects.json b/providers/packet/src/test/resources/projects.json new file mode 100644 index 0000000000..f7f17104a1 --- /dev/null +++ b/providers/packet/src/test/resources/projects.json @@ -0,0 +1 @@ +{"projects":[{"id":"93907f48-adfe-43ed-ad89-0e6e83721a54","name":"Cloudsoft CCS Testing","created_at":"2016-09-15T08:50:58Z","updated_at":"2017-01-05T09:36:53Z","max_devices":{"baremetal_0":null,"baremetal_1":null,"baremetal_2":null,"baremetal_3":null,"baremetal_2a":null,"storage_1":null,"storage_2":null},"members":[{"href":"/users/1140617d-262d-4502-a3d6-771d83c930da"},{"href":"/users/343345fe-18b3-46a3-9b9c-e4a2fe88ccbd"},{"href":"/users/73b0442e-cc4b-42a0-8d3a-c8dfb8a4ff2e"},{"href":"/users/ad711bc3-6333-449a-a405-23ca81f38c00"}],"memberships":[{"href":"/memberships/914facae-547f-46fc-93e8-860eb53d9bf6"},{"href":"/memberships/1ce7c9d9-d11f-47f2-b5a6-d1221338ad69"},{"href":"/memberships/e25c4478-9e44-465a-a9d7-bffc7a83300d"},{"href":"/memberships/8a00c05b-3ddc-41c3-8dd7-4a3d5984ecec"}],"invitations":[],"payment_method":{"href":"/payment-methods/b2bb4ee0-506b-4c01-b17d-ba41d5c430c5"},"devices":[{"href":"/devices/4942a6c7-55c6-42cc-a06b-cccd6f2fa5df"}],"ssh_keys":[{"href":"/ssh-keys/070e3282-5b6a-4f75-8f18-a4e7488eafaa"}],"volumes":[],"href":"/projects/93907f48-adfe-43ed-ad89-0e6e83721a54"}],"meta":{"first":{"href":"/projects?page=1"},"previous":null,"self":{"href":"/projects?page=1"},"next":null,"last":{"href":"/projects?page=1"},"total":1}}