Skip to content
This repository was archived by the owner on Feb 4, 2019. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.googlecomputeengine.functions.internal;
package org.jclouds.googlecomputeengine;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.jclouds.Fallbacks.valOnNotFoundOr404;

import javax.ws.rs.HttpMethod;
import org.jclouds.Fallback;
import org.jclouds.googlecomputeengine.domain.ListPage;
import org.jclouds.googlecomputeengine.domain.Resource.Kind;

public interface GoogleComputeEngineFallbacks {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

git seems to know something I don't know!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes :) I removed the PATCH class as it already exists in jclouds-core. That seems to have fooled git.


public static final class EmptyListPageOnNotFoundOr404 implements Fallback<ListPage<Object>> {
public ListPage<Object> createOrPropagate(Throwable t) throws Exception {
// The Kind attribute is mandatory but dummy, as it will be never be used
return valOnNotFoundOr404(new ListPage.Empty<Object>(Kind.ADDRESS_LIST), checkNotNull(t, "throwable"));
}
}

/**
* Indicates that the annotated method responds to HTTP PATCH requests
*
* @see javax.ws.rs.HttpMethod
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@HttpMethod("PATCH")
public @interface PATCH {
}
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,8 @@ public void suspendNode(String name) {
private LoginCredentials getFromImageAndOverrideIfRequired(org.jclouds.compute.domain.Image image,
GoogleComputeEngineTemplateOptions options) {
LoginCredentials defaultCredentials = image.getDefaultCredentials();
String[] keys = defaultCredentials.getPrivateKey().split(":");
// Atthis point the private key must exist
String[] keys = defaultCredentials.getOptionalPrivateKey().get().split(":");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine, but I will sidebar: particularly with java8 having optional, I feel we should divest or at least stop proliferating this type. Nullable is fine. I don't want to get to a very long debate about it, just raising my opinion on the matter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. My intent was only to remove the warnings, but not start a major cleanup of the provider, but I'd be happy to start a global cleanup PR in the provider to address all things we want to improve.

String publicKey = keys[0];
String privateKey = keys[1];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,7 @@ public Set<SecurityGroup> listSecurityGroupsForNode(String id) {
return ImmutableSet.of();
}

ImmutableSet.Builder builder = ImmutableSet.builder();

ImmutableSet.Builder<SecurityGroup> builder = ImmutableSet.builder();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another thing that is fine, but I'd say in the future we should stop Set'ing everything. List is fine for things like this.


for (NetworkInterface nwInterface : instance.getNetworkInterfaces()) {
String networkUrl = nwInterface.getNetwork().getPath();
Expand Down Expand Up @@ -171,7 +170,7 @@ public boolean removeSecurityGroup(String id) {

ListOptions options = new ListOptions.Builder().filter("network eq .*/" + id);

FluentIterable<Firewall> fws = api.getFirewallApiForProject(userProject.get()).list(options).concat();
FluentIterable<Firewall> fws = api.getFirewallApiForProject(userProject.get()).list(options).toPagedIterable().concat();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer FluentIterable.from(api...concat()); or FluentIterable.from(api...flatten()); I think we should stop proliferating FluentIterable as it can easily wrap things. When we don't make special hooks for FluentIterable, we reduce the surface area we have wrt guava compatibility. (again. not interested in an angry debate with people, just offering advice).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean this?

FluentIterable.from(api.getFirewallApiForProject(userProject.get())
   .list(options)
   .toPagedIterable()
   .concat());

The PagedIterable#concat already returns a FluentIterable, so that won't be needed (and that method in Guava is deprecated). Are you in the direction of changing the PagedIterable.concat() method to have a regular Iterable in the signature? Or am I missing something?


for (Firewall fw : fws) {
AtomicReference<Operation> operation = Atomics.newReference(api.getFirewallApiForProject(userProject.get())
Expand Down Expand Up @@ -203,7 +202,8 @@ public SecurityGroup addIpPermission(IpPermission ipPermission, SecurityGroup gr

ListOptions options = new ListOptions.Builder().filter("network eq .*/" + group.getName());

if (api.getFirewallApiForProject(userProject.get()).list(options).concat().anyMatch(providesIpPermission(ipPermission))) {
FluentIterable<Firewall> fws = api.getFirewallApiForProject(userProject.get()).list(options).toPagedIterable().concat();
if (fws.anyMatch(providesIpPermission(ipPermission))) {
// Permission already exists.
return group;
}
Expand Down Expand Up @@ -268,7 +268,7 @@ public SecurityGroup removeIpPermission(IpPermission ipPermission, SecurityGroup

ListOptions options = new ListOptions.Builder().filter("network eq .*/" + group.getName());

FluentIterable<Firewall> fws = api.getFirewallApiForProject(userProject.get()).list(options).concat();
FluentIterable<Firewall> fws = api.getFirewallApiForProject(userProject.get()).list(options).toPagedIterable().concat();

for (Firewall fw : fws) {
if (equalsIpPermission(ipPermission).apply(fw)) {
Expand Down Expand Up @@ -328,7 +328,7 @@ public boolean supportsExclusionCidrBlocks() {

private SecurityGroup groupForTagsInNetwork(Network nw, final Set <String> tags) {
ListOptions opts = new Builder().filter("network eq .*/" + nw.getName());
Set<Firewall> fws = api.getFirewallApiForProject(userProject.get()).list(opts).concat()
Set<Firewall> fws = api.getFirewallApiForProject(userProject.get()).list(opts).toPagedIterable().concat()
.filter(new Predicate<Firewall>() {
@Override
public boolean apply(final Firewall input) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
public class BuildInstanceMetadata implements Function<TemplateOptions, ImmutableMap.Builder<String, String>> {

@Override
public ImmutableMap.Builder apply(TemplateOptions input) {
public ImmutableMap.Builder<String, String> apply(TemplateOptions input) {
ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
if (input.getPublicKey() != null) {
builder.put("sshKeys", format("%s:%s %s@localhost", checkNotNull(input.getLoginUser(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public FirewallToIpPermission() {

@Override
public Iterable<IpPermission> apply(Firewall fw) {
ImmutableSet.Builder setBuilder = ImmutableSet.builder();
ImmutableSet.Builder<IpPermission> setBuilder = ImmutableSet.builder();

for (Rule rule : fw.getAllowed()) {
if (!rule.getPorts().isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@
import org.jclouds.compute.domain.NodeMetadataBuilder;
import org.jclouds.compute.functions.GroupNamingConvention;
import org.jclouds.domain.Location;
import org.jclouds.googlecomputeengine.GoogleComputeEngineApi;
import org.jclouds.googlecomputeengine.config.UserProject;
import org.jclouds.googlecomputeengine.domain.Instance;
import org.jclouds.googlecomputeengine.domain.InstanceInZone;
import org.jclouds.googlecomputeengine.domain.SlashEncodedIds;
Expand All @@ -56,26 +54,20 @@ public class InstanceInZoneToNodeMetadata implements Function<InstanceInZone, No
private final Supplier<Map<URI, ? extends Hardware>> hardwares;
private final Supplier<Map<URI, ? extends Location>> locations;
private final FirewallTagNamingConvention.Factory firewallTagNamingConvention;
private final GoogleComputeEngineApi api;
private final Supplier<String> userProject;

@Inject
public InstanceInZoneToNodeMetadata(Map<Instance.Status, NodeMetadata.Status> toPortableNodeStatus,
GroupNamingConvention.Factory namingConvention,
@Memoized Supplier<Map<URI, ? extends Image>> images,
@Memoized Supplier<Map<URI, ? extends Hardware>> hardwares,
@Memoized Supplier<Map<URI, ? extends Location>> locations,
FirewallTagNamingConvention.Factory firewallTagNamingConvention,
GoogleComputeEngineApi api,
@UserProject Supplier<String> userProject) {
FirewallTagNamingConvention.Factory firewallTagNamingConvention) {
this.toPortableNodeStatus = toPortableNodeStatus;
this.nodeNamingConvention = namingConvention.createWithoutPrefix();
this.images = images;
this.hardwares = hardwares;
this.locations = locations;
this.firewallTagNamingConvention = checkNotNull(firewallTagNamingConvention, "firewallTagNamingConvention");
this.api = checkNotNull(api, "api");
this.userProject = checkNotNull(userProject, "userProject");
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
*/
package org.jclouds.googlecomputeengine.compute.functions;

import static com.google.common.base.Preconditions.checkNotNull;

import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Named;
Expand All @@ -33,6 +35,7 @@

import com.google.common.base.Function;
import com.google.common.base.Supplier;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableSet;

/**
Expand All @@ -52,9 +55,9 @@ public class NetworkToSecurityGroup implements Function<Network, SecurityGroup>
public NetworkToSecurityGroup(Function<Firewall, Iterable<IpPermission>> firewallToPerms,
GoogleComputeEngineApi api,
@UserProject Supplier<String> project) {
this.firewallToPerms = firewallToPerms;
this.api = api;
this.project = project;
this.firewallToPerms = checkNotNull(firewallToPerms, "firewallToPerms");
this.api = checkNotNull(api, "api");
this.project = checkNotNull(project, "project");
}

@Override
Expand All @@ -66,11 +69,12 @@ public SecurityGroup apply(Network network) {
builder.name(network.getName());
builder.uri(network.getSelfLink());

ImmutableSet.Builder permBuilder = ImmutableSet.builder();
ImmutableSet.Builder<IpPermission> permBuilder = ImmutableSet.builder();

ListOptions options = new ListOptions.Builder().filter("network eq .*/" + network.getName());
FluentIterable<Firewall> fws = api.getFirewallApiForProject(project.get()).list(options).toPagedIterable().concat();

for (Firewall fw : api.getFirewallApiForProject(project.get()).list(options).concat()) {
for (Firewall fw : fws) {
permBuilder.addAll(firewallToPerms.apply(fw));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
public class GoogleComputeEngineTemplateOptions extends TemplateOptions {

private Optional<URI> network = Optional.absent();
private Optional<String> networkName = Optional.absent();
private Set<Instance.ServiceAccount> serviceAccounts = Sets.newLinkedHashSet();
private boolean enableNat = true;
private Set<PersistentDisk> disks = Sets.newLinkedHashSet();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,120 +16,59 @@
*/
package org.jclouds.googlecomputeengine.domain;

import static com.google.common.base.MoreObjects.toStringHelper;
import static com.google.common.base.Objects.equal;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.jclouds.googlecomputeengine.domain.Resource.Kind;

import java.beans.ConstructorProperties;
import java.util.Iterator;

import org.jclouds.collect.IterableWithMarker;
import org.jclouds.collect.IterableWithMarkers;
import org.jclouds.collect.PagedIterable;
import org.jclouds.collect.PagedIterables;
import org.jclouds.googlecomputeengine.domain.Resource.Kind;

import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;

/**
* The collection returned from any <code>listFirstPage()</code> method.
* A single page returned from a paginated collection that knows how to advance
* to the next page.
*/
public class ListPage<T> extends IterableWithMarker<T> {

private final Kind kind;
private final String nextPageToken;
private final Iterable<T> items;
private final PageWithMarker<T> delegate;
private final Function<PageWithMarker<T>, PagedIterable<T>> advancingFunction;

@ConstructorProperties({ "kind", "nextPageToken", "items" })
protected ListPage(Kind kind, String nextPageToken, Iterable<T> items) {
this.kind = checkNotNull(kind, "kind");
this.nextPageToken = nextPageToken;
this.items = items != null ? ImmutableList.copyOf(items) : ImmutableList.<T>of();
public ListPage(PageWithMarker<T> delegate, Function<PageWithMarker<T>, PagedIterable<T>> advancingFunction) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prefer to separate listpage so that it can be a simple data type. offering an additional method or otherwise mean to construct an advancing list would be more ideal than having all lists have additional internal state that includes references to jclouds context internals. For example, a reference to this list will keep a lot of a jclouds context from being gc'd, and the user may not suspect that. I'm not saying we don't already do things like this. Just suggesting that today, not sure we would always prefer to conflate the list with its advancing function for all use cases. Simple way out is to make an alternate method that returns a normal list.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to be able to build a PagedIterable given single page (IterableWithMarker or ListPage) we need the advancing function. Wether it is already set inside the list object or we use an alternate method to build it (such as PagedIterables#advance) we need to hold a reference to that function as long as the List object exists; otherwise it won't be able to lazily fetch new pages as they are needed.

This said, I tried to hide the advancing function from users because it might be difficult to get:

  • Users calling a list method would need some deeper knowledge on how it works internally to be able to pick the right function.
  • When directly building the Api you don't have access to the Injector (however, if you build a Context such as the compute one, you have the utils().injector() method), so there might not be an easy way to get a proper instance the right function to use.

That made me go for the ListPage approach having the toPagedIterable method and already having a reference to the advancing function.

Just to make sure I understand your concern: we could be leaking the reference to the advancing function only in the case the user does not want to advance to the next page. When getting the "entire" list provided by the PagedIterable, the advancing function is always needed. IMO, the former use case might not be the most used one, as the method that returns the ListPage is the one used when filtering resources or performing searches, but that's only a guess; we really don't know which of both methods will be used more.

I see two options to move forward:

  • Keep the ListPage as-is, in favor of api usage simplicity, but with the possibility of holding an unneeded reference to the advancing function.
  • Get rid of that ListPage and make the api methods just return a regular IterableWithMarker. This will make users have to use the existing PagedIterables.advance to build a Pagediterable, and manually get the advancing function by some mean.

Am I getting the picture? WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will followup more completely later.. maybe :) since maybe I will answer
best now.

One thing to keep in mind that in many cases there will be only one page of
results. In this case the leak risk, as well the bloated class is
unnecessary.

It may be possible in the current design to special case this.

Also we had a meetup with clojure folks back in the day (specifically toni
batchelli) who specifically asked to not have auto advancing mandatory as
it introduces risk of crossing network boundaries on simple iteration. They
preferred to manage their own pagination.

In all, I think this change is a step backwards or at best neutral, from a
user pov. It Is less copy paste from implementer pov.

I am not focused on it or the several other patterns we use, and admit that
we can defer a better list for a new version (maybe v2)

In the mean time, feel free to merge this, taking into consideration what
you feel is most time and version relevant. We can always copy paste these
methods back.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, I first implemented it by returning a regular IterableWithMarker (the usual approach without references to the advancing function in the iterable itself), but I when I started modifying the live and expect tests I decided to change that, as I found it really ugly to keep the current tests with the new approach. All rely in the PagedIterable even when getting one page, and the code turned non-readable when having to manually get the advancing function.

Anyway, I'm OK to change to that approach, but I'll also refactor the majority of the tests. Most of the expect tests are a mess, and live tests that use lists have a lot of improvement, so I'll change them and refactor all expect tests to MWS ones.

It will take some more time, but we'll have a cleaner provider and the tests in the way we want them :)

Thanks for the feedback!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no problemo. I like that you are thinking about things bigger than (but
also including) maintenance drift. Thanks for that!

this.delegate = checkNotNull(delegate, "delegate");
this.advancingFunction = checkNotNull(advancingFunction, "advancingFunction");
}

public Kind getKind() {
return kind;
}

@Override
public Optional<Object> nextMarker() {
return Optional.<Object>fromNullable(nextPageToken);
public PagedIterable<T> toPagedIterable() {
return advancingFunction.apply(delegate);
}

@Override
public Iterator<T> iterator() {
return checkNotNull(items, "items").iterator();
}

@Override
public int hashCode() {
return Objects.hashCode(kind, items);
return delegate.iterator();
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null || getClass() != obj.getClass())
return false;
ListPage<?> that = ListPage.class.cast(obj);
return equal(this.kind, that.kind) && equal(this.items, that.items);
}

protected MoreObjects.ToStringHelper string() {
return toStringHelper(this).omitNullValues().add("kind", kind).add("nextPageToken", nextPageToken)
.add("items", items);
}

@Override
public String toString() {
return string().toString();
}

public static <T> Builder<T> builder() {
return new Builder<T>();
}

public Builder<T> toBuilder() {
return new Builder<T>().fromPagedList(this);
public Optional<Object> nextMarker() {
return delegate.nextMarker();
}

public static final class Builder<T> {

private Kind kind;
private String nextPageToken;
private ImmutableList.Builder<T> items = ImmutableList.builder();

public Builder<T> kind(Kind kind) {
this.kind = kind;
return this;
}

public Builder<T> addItem(T item) {
this.items.add(item);
return this;
}

public Builder<T> items(Iterable<T> items) {
this.items.addAll(items);
return this;
}
public static class Empty<T> extends ListPage<T> {

public Builder<T> nextPageToken(String nextPageToken) {
this.nextPageToken = nextPageToken;
return this;
public Empty(Kind kind) {
super(PageWithMarker.<T>builder().kind(kind).build(), new Function<PageWithMarker<T>, PagedIterable<T>>() {
@Override
public PagedIterable<T> apply(PageWithMarker<T> input) {
return PagedIterables.onlyPage(IterableWithMarkers.from(ImmutableSet.<T>of()));
}
});
}

public ListPage<T> build() {
return new ListPage<T>(kind, nextPageToken, items.build());
}

public Builder<T> fromPagedList(ListPage<T> in) {
return this
.kind(in.getKind())
.nextPageToken((String) in.nextMarker().orNull())
.items(in);

}
}

}
Loading