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
This commit is contained in:
Andrea Turli 2017-01-10 11:11:18 +01:00
parent 1620a19787
commit 34d013f7db
22 changed files with 1760 additions and 0 deletions

View File

@ -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.
* <p>
*
* @see <a href="https://www.packet.net/help/api/" >doc</a>
*/
public interface PacketApi extends Closeable {
/**
* The Packet API includes operations for managing project.
*
* @see <a href="https://www.packet.net/help/api/#page:projects,header:projects-projects">docs</a>
*/
@Delegate
ProjectApi projectApi();
}

View File

@ -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<PacketApi> {
@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<PacketApi, 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.<Class<? extends Module>>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;
}
}
}

View File

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

View File

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

View File

@ -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<PacketApi> {
@Override
protected void configure() {
super.configure();
bind(ImplicitLocationSupplier.class).to(FirstRegion.class).in(Scopes.SINGLETON);
bind(new TypeLiteral<Function<Href, ListOptions>>() {
}).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);
}
}

View File

@ -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<T> extends IterableWithMarker<T> {
@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<T> items;
private final Meta meta;
protected PaginatedCollection(List<T> items, Meta meta) {
this.items = ImmutableList.copyOf(checkNotNull(items, "items cannot be null"));
this.meta = checkNotNull(meta, "meta cannot be null");
}
public List<T> items() {
return items;
}
public Meta meta() {
return meta;
}
@Override
public Iterator<T> iterator() {
return items.iterator();
}
@Override
public Optional<Object> nextMarker() {
if (meta.next() == null) {
return Optional.absent();
}
return Optional.fromNullable((Object) meta.next());
}
}

View File

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

View File

@ -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<Project> list();
@Named("project:list")
@GET
@ResponseParser(ParseProjects.class)
@Fallback(Fallbacks.EmptyIterableWithMarkerOnNotFoundOr404.class)
IterableWithMarker<Project> list(ListOptions options);
final class ParseProjects extends ParseJson<ParseProjects.Projects> {
@Inject
ParseProjects(Json json) {
super(json, TypeLiteral.get(Projects.class));
}
private static class Projects extends PaginatedCollection<Project> {
@ConstructorProperties({ "projects", "meta" })
public Projects(List<Project> items, Meta meta) {
super(items, meta);
}
}
private static class ToPagedIterable extends BaseToPagedIterable<Project, ListOptions> {
@Inject ToPagedIterable(PacketApi api, Function<Href, ListOptions> linkToOptions) {
super(api, linkToOptions);
}
@Override
protected IterableWithMarker<Project> fetchPageUsingOptions(ListOptions options, Optional<Object> arg0) {
return api.projectApi().list(options);
}
}
}
}

View File

@ -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<Credentials> creds;
@Inject
AddXAuthTokenToRequest(@Provider Supplier<Credentials> 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();
}
}

View File

@ -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
* <code>PagedIterable</code>. 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<T, O extends ListOptions> extends
Arg0ToPagedIterable<T, BaseToPagedIterable<T, O>> {
private final Function<Href, O> linkToOptions;
protected final PacketApi api;
@Inject protected BaseToPagedIterable(PacketApi api, Function<Href, O> linkToOptions) {
this.api = api;
this.linkToOptions = linkToOptions;
}
protected abstract IterableWithMarker<T> fetchPageUsingOptions(O options, Optional<Object> arg0);
@Override
protected Function<Object, IterableWithMarker<T>> markerToNextForArg0(final Optional<Object> arg0) {
return new Function<Object, IterableWithMarker<T>>() {
@Override
public IterableWithMarker<T> apply(Object input) {
O nextOptions = linkToOptions.apply(Href.class.cast(input));
return fetchPageUsingOptions(nextOptions, arg0);
}
};
}
}

View File

@ -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<Href, ListOptions> {
@Override
public ListOptions apply(Href input) {
checkNotNull(input, "input cannot be null");
Multimap<String, String> 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<String, String> params) {
return params.containsKey(key) ? emptyToNull(getFirst(params.get(key), null)) : null;
}
}

View File

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

View File

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

View File

@ -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<PacketApi> {
private Predicate<String> deviceRunning;
private Predicate<String> 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<Module> modules) {
Injector injector = newBuilder().modules(modules).overrides(props).buildInjector();
deviceRunning = injector.getInstance(Key.get(new TypeLiteral<Predicate<String>>(){},
Names.named(TIMEOUT_NODE_RUNNING)));
deviceTerminated = injector.getInstance(Key.get(new TypeLiteral<Predicate<String>>(){},
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));
}
}

View File

@ -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<Module> modules = ImmutableSet.<Module> 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<PacketApi> 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> T onlyObjectFromResource(String resourceName, TypeToken<Map<String, T>> type) {
// Assume JSON objects passed here will be in the form: { "entity": { ... } }
String text = stringFromResource(resourceName);
Map<String, T> 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> T objectFromResource(String resourceName, Class<T> 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;
}
}

View File

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

View File

@ -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<Project> 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<Project> 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<Project> 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<Project> actions = api.projectApi().list(page(1).perPage(5));
assertTrue(isEmpty(actions));
assertEquals(server.getRequestCount(), 1);
assertSent(server, "GET", "/projects?page=1&per_page=5");
}
}

View File

@ -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<String, String> 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<String, String> params = options.buildQueryParameters();
assertEquals(getOnlyElement(params.get(PAGE_PARAM)), "2");
assertEquals(getOnlyElement(params.get(PER_PAGE_PARAM)), "5");
}
}

View File

@ -0,0 +1,42 @@
<?xml version="1.0"?>
<configuration scan="false">
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>target/test-data/jclouds.log</file>
<encoder>
<Pattern>%d %-5p [%c] [%thread] %m%n</Pattern>
</encoder>
</appender>
<appender name="WIREFILE" class="ch.qos.logback.core.FileAppender">
<file>target/test-data/jclouds-wire.log</file>
<encoder>
<Pattern>%d %-5p [%c] [%thread] %m%n</Pattern>
</encoder>
</appender>
<appender name="COMPUTEFILE" class="ch.qos.logback.core.FileAppender">
<file>target/jclouds-compute.log</file>
<encoder>
<Pattern>%d %-5p [%c] [%thread] %m%n</Pattern>
</encoder>
</appender>
<logger name="org.jclouds">
<level value="DEBUG" />
<appender-ref ref="FILE" />
</logger>
<logger name="jclouds.compute">
<level value="DEBUG" />
<appender-ref ref="COMPUTEFILE" />
</logger>
<logger name="jclouds.wire">
<level value="DEBUG" />
<appender-ref ref="WIREFILE" />
</logger>
<logger name="jclouds.headers">
<level value="DEBUG" />
<appender-ref ref="WIREFILE" />
</logger>
<root>
<level value="INFO" />
</root>
</configuration>

View File

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

View File

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

View File

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