Updated Branches: refs/heads/master cbac63e18 -> 44021fc9e
JCLOUDS-299. implement multi-region support in openstack-swift Project: http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/commit/44021fc9 Tree: http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/tree/44021fc9 Diff: http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/diff/44021fc9 Branch: refs/heads/master Commit: 44021fc9eeaf9cc7886fd849323a2cdfc8bf97c7 Parents: cbac63e Author: Adrian Cole <[email protected]> Authored: Mon Sep 30 12:06:42 2013 -0700 Committer: Adrian Cole <[email protected]> Committed: Mon Sep 30 12:37:16 2013 -0700 ---------------------------------------------------------------------- .../openstack/swift/v1/SwiftApiMetadata.java | 4 +- .../blobstore/RegionScopedBlobStoreContext.java | 177 ++++++++++++++++++ .../blobstore/RegionScopedSwiftBlobStore.java | 181 ++++++++++++++----- .../RegionScopedTemporaryUrlBlobSigner.java | 31 ++-- .../config/SignUsingTemporaryUrls.java | 63 +++---- .../config/SwiftBlobStoreContextModule.java | 41 ++++- .../blobstore/functions/ToResourceMetadata.java | 7 +- .../internal/SubmissionAsyncBlobStore.java | 2 +- .../RegionScopedBlobStoreContextLiveTest.java | 95 ++++++++++ 9 files changed, 494 insertions(+), 107 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/44021fc9/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftApiMetadata.java ---------------------------------------------------------------------- diff --git a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftApiMetadata.java b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftApiMetadata.java index a3ce97c..e2a1bbd 100644 --- a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftApiMetadata.java +++ b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftApiMetadata.java @@ -24,11 +24,11 @@ import java.net.URI; import java.util.Properties; import org.jclouds.apis.ApiMetadata; -import org.jclouds.blobstore.BlobStoreContext; import org.jclouds.openstack.keystone.v2_0.config.AuthenticationApiModule; import org.jclouds.openstack.keystone.v2_0.config.CredentialTypes; import org.jclouds.openstack.keystone.v2_0.config.KeystoneAuthenticationModule; import org.jclouds.openstack.keystone.v2_0.config.KeystoneAuthenticationModule.RegionModule; +import org.jclouds.openstack.swift.v1.blobstore.RegionScopedBlobStoreContext; import org.jclouds.openstack.swift.v1.blobstore.config.SignUsingTemporaryUrls; import org.jclouds.openstack.swift.v1.blobstore.config.SwiftBlobStoreContextModule; import org.jclouds.openstack.swift.v1.config.SwiftHttpApiModule; @@ -79,7 +79,7 @@ public class SwiftApiMetadata extends BaseHttpApiMetadata<SwiftApi> { .endpointName("KeyStone base url ending in /v2.0/") .defaultEndpoint("http://localhost:5000/v2.0/") .defaultProperties(SwiftApiMetadata.defaultProperties()) - .view(typeToken(BlobStoreContext.class)) + .view(typeToken(RegionScopedBlobStoreContext.class)) .defaultModules(ImmutableSet.<Class<? extends Module>>builder() .add(AuthenticationApiModule.class) .add(KeystoneAuthenticationModule.class) http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/44021fc9/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContext.java ---------------------------------------------------------------------- diff --git a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContext.java b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContext.java new file mode 100644 index 0000000..11e54d6 --- /dev/null +++ b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContext.java @@ -0,0 +1,177 @@ +/* + * 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.openstack.swift.v1.blobstore; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static org.jclouds.Constants.PROPERTY_USER_THREADS; + +import java.util.Set; + +import javax.inject.Inject; +import javax.inject.Named; + +import org.jclouds.Context; +import org.jclouds.blobstore.BlobRequestSigner; +import org.jclouds.blobstore.BlobStore; +import org.jclouds.blobstore.BlobStoreContext; +import org.jclouds.blobstore.attr.ConsistencyModel; +import org.jclouds.internal.BaseView; +import org.jclouds.location.Provider; +import org.jclouds.location.Region; +import org.jclouds.rest.Utils; + +import com.google.common.base.Function; +import com.google.common.base.Supplier; +import com.google.common.reflect.TypeToken; +import com.google.common.util.concurrent.ListeningExecutorService; + +/** + * Implementation of {@link BlobStoreContext} which allows you to employ + * multiple regions. + * + * Example. + * + * <pre> + * ctx = contextBuilder.buildView(RegionScopedBlobStoreContext.class); + * + * Set<String> regionIds = ctx.configuredRegions(); + * + * // isolated to a specific region + * BlobStore texasBlobStore = ctx.blobStoreInRegion("US-TX"); + * BlobStore virginiaBlobStore = ctx.blobStoreInRegion("US-VA"); + * </pre> + */ +public class RegionScopedBlobStoreContext extends BaseView implements BlobStoreContext { + + /** + * @return regions supported in this context. + */ + public Set<String> configuredRegions() { + return regionIds.get(); + } + + /** + * @param regionId + * valid region id from {@link #configuredRegions()} + * @throws IllegalArgumentException + * if {@code regionId} was invalid. + */ + public BlobStore blobStoreInRegion(String regionId) { + checkRegionId(regionId); + return blobStore.apply(regionId); + } + + /** + * @param regionId + * valid region id from {@link #configuredRegions()} + * @throws IllegalArgumentException + * if {@code regionId} was invalid. + */ + public BlobRequestSigner signerInRegion(String regionId) { + checkRegionId(regionId); + return blobRequestSigner.apply(regionId); + } + + /** + * @param regionId + * valid region id from {@link #configuredRegions()} + * @throws IllegalArgumentException + * if {@code regionId} was invalid. longer supported. Please use + * {@link org.jclouds.blobstore.BlobStore} + */ + @Deprecated + public org.jclouds.blobstore.AsyncBlobStore asyncBlobStoreInRegion(String regionId) { + checkRegionId(regionId); + return new org.jclouds.openstack.swift.v1.blobstore.internal.SubmissionAsyncBlobStore( + blobStoreInRegion(regionId), executor); + } + + protected void checkRegionId(String regionId) { + checkArgument(configuredRegions().contains(checkNotNull(regionId, "regionId was null")), "region %s not in %s", + regionId, configuredRegions()); + } + + private final Supplier<Set<String>> regionIds; + private final Supplier<String> implicitRegionId; + // factory functions are decoupled so that you can exchange how requests are + // signed or decorate without a class hierarchy dependency + private final Function<String, BlobStore> blobStore; + private final Function<String, BlobRequestSigner> blobRequestSigner; + private final Utils utils; + private final ListeningExecutorService executor; + + @Inject + public RegionScopedBlobStoreContext(@Provider Context backend, @Provider TypeToken<? extends Context> backendType, + @Region Supplier<Set<String>> regionIds, @Region Supplier<String> implicitRegionId, + Function<String, BlobStore> blobStore, Function<String, BlobRequestSigner> blobRequestSigner, Utils utils, + @Named(PROPERTY_USER_THREADS) ListeningExecutorService executor) { + super(backend, backendType); + this.regionIds = checkNotNull(regionIds, "regionIds"); + this.implicitRegionId = checkNotNull(implicitRegionId, "implicitRegionId"); + this.blobStore = checkNotNull(blobStore, "blobStore"); + this.blobRequestSigner = checkNotNull(blobRequestSigner, "blobRequestSigner"); + this.utils = checkNotNull(utils, "utils"); + this.executor = checkNotNull(executor, "executor"); + } + + @Override + public ConsistencyModel getConsistencyModel() { + return ConsistencyModel.STRICT; + } + + @Override + public BlobStore getBlobStore() { + return blobStoreInRegion(implicitRegionId.get()); + } + + @Override + public BlobRequestSigner getSigner() { + return signerInRegion(implicitRegionId.get()); + } + + @Override + @Deprecated + public org.jclouds.blobstore.AsyncBlobStore getAsyncBlobStore() { + return asyncBlobStoreInRegion(implicitRegionId.get()); + } + + @Override + public Utils utils() { + return utils; + } + + @Override + public void close() { + delegate().close(); + } + + public int hashCode() { + return delegate().hashCode(); + } + + @Override + public String toString() { + return delegate().toString(); + } + + @Override + public boolean equals(Object obj) { + return delegate().equals(obj); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/44021fc9/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java ---------------------------------------------------------------------- diff --git a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java index ad88550..eedfa27 100644 --- a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java +++ b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java @@ -17,29 +17,39 @@ package org.jclouds.openstack.swift.v1.blobstore; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Iterables.tryFind; +import static com.google.common.collect.Lists.transform; +import static org.jclouds.blobstore.options.ListContainerOptions.Builder.recursive; +import static org.jclouds.location.predicates.LocationPredicates.idEquals; import java.util.List; import java.util.Set; import javax.inject.Inject; +import org.jclouds.blobstore.BlobStore; import org.jclouds.blobstore.BlobStoreContext; import org.jclouds.blobstore.domain.Blob; +import org.jclouds.blobstore.domain.BlobBuilder; import org.jclouds.blobstore.domain.BlobMetadata; import org.jclouds.blobstore.domain.MutableBlobMetadata; import org.jclouds.blobstore.domain.PageSet; import org.jclouds.blobstore.domain.StorageMetadata; +import org.jclouds.blobstore.domain.StorageType; +import org.jclouds.blobstore.domain.internal.BlobBuilderImpl; import org.jclouds.blobstore.domain.internal.BlobImpl; import org.jclouds.blobstore.domain.internal.PageSetImpl; import org.jclouds.blobstore.functions.BlobToHttpGetOptions; -import org.jclouds.blobstore.internal.BaseBlobStore; import org.jclouds.blobstore.options.CreateContainerOptions; import org.jclouds.blobstore.options.GetOptions; import org.jclouds.blobstore.options.ListContainerOptions; import org.jclouds.blobstore.options.PutOptions; -import org.jclouds.blobstore.strategy.internal.FetchBlobMetadata; -import org.jclouds.blobstore.util.BlobUtils; +import org.jclouds.blobstore.strategy.ClearListStrategy; +import org.jclouds.collect.Memoized; import org.jclouds.domain.Location; +import org.jclouds.io.Payload; +import org.jclouds.io.payloads.ByteArrayPayload; import org.jclouds.openstack.swift.v1.SwiftApi; import org.jclouds.openstack.swift.v1.blobstore.functions.ToBlobMetadata; import org.jclouds.openstack.swift.v1.blobstore.functions.ToListContainerOptions; @@ -57,50 +67,57 @@ import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Lists; +import com.google.inject.AbstractModule; +import com.google.inject.Injector; +import com.google.inject.assistedinject.Assisted; -public class RegionScopedSwiftBlobStore extends BaseBlobStore { - - private final BlobToHttpGetOptions toGetOptions = new BlobToHttpGetOptions(); - private final ToListContainerOptions toListContainerOptions = new ToListContainerOptions(); - - private final SwiftApi api; - private final Supplier<Location> region; - private final ToResourceMetadata toResourceMetadata; - private final FetchBlobMetadata fetchMetadata; +public class RegionScopedSwiftBlobStore implements BlobStore { @Inject - protected RegionScopedSwiftBlobStore(BlobStoreContext context, BlobUtils blobUtils, final Supplier<Location> region, - SwiftApi api, FetchBlobMetadata fetchMetadata) { - super(context, blobUtils, region, new Supplier<Set<? extends Location>>() { + protected RegionScopedSwiftBlobStore(Injector baseGraph, BlobStoreContext context, SwiftApi api, + @Memoized Supplier<Set<? extends Location>> locations, @Assisted String regionId) { + checkNotNull(regionId, "regionId"); + Optional<? extends Location> found = tryFind(locations.get(), idEquals(regionId)); + checkArgument(found.isPresent(), "region %s not in %s", regionId, locations.get()); + this.region = found.get(); + this.toResourceMetadata = new ToResourceMetadata(found.get()); + this.context = context; + this.api = api; + // until we parameterize ClearListStrategy with a factory + this.clearList = baseGraph.createChildInjector(new AbstractModule() { @Override - public Set<? extends Location> get() { - return ImmutableSet.of(region.get()); + protected void configure() { + bind(BlobStore.class).toInstance(RegionScopedSwiftBlobStore.this); } - }); - this.api = api; - this.toResourceMetadata = new ToResourceMetadata(region); - this.region = region; - this.fetchMetadata = fetchMetadata; + }).getInstance(ClearListStrategy.class); } - /** all commands are scoped to a region. */ - protected String regionId() { - return region.get().getId(); + private final BlobStoreContext context; + private final ClearListStrategy clearList; + private final SwiftApi api; + private final Location region; + private final BlobToHttpGetOptions toGetOptions = new BlobToHttpGetOptions(); + private final ToListContainerOptions toListContainerOptions = new ToListContainerOptions(); + private final ToResourceMetadata toResourceMetadata; + + @Override + public Set<? extends Location> listAssignableLocations() { + return ImmutableSet.of(region); } @Override public PageSet<? extends StorageMetadata> list() { // TODO: there may eventually be >10k containers.. - FluentIterable<StorageMetadata> containers = api.containerApiInRegion(regionId()).listFirstPage() + FluentIterable<StorageMetadata> containers = api.containerApiInRegion(region.getId()).listFirstPage() .transform(toResourceMetadata); return new PageSetImpl<StorageMetadata>(containers, null); } @Override public boolean containerExists(String container) { - Container val = api.containerApiInRegion(regionId()).get(container); + Container val = api.containerApiInRegion(region.getId()).get(container); containerCache.put(container, Optional.fromNullable(val)); return val != null; } @@ -112,11 +129,11 @@ public class RegionScopedSwiftBlobStore extends BaseBlobStore { @Override public boolean createContainerInLocation(Location location, String container, CreateContainerOptions options) { - checkArgument(location == null || location.equals(region.get()), "location must be null or %s", region.get()); + checkArgument(location == null || location.equals(region), "location must be null or %s", region); if (options.isPublicRead()) { - return api.containerApiInRegion(regionId()).createIfAbsent(container, ANYBODY_READ); + return api.containerApiInRegion(region.getId()).createIfAbsent(container, ANYBODY_READ); } - return api.containerApiInRegion(regionId()).createIfAbsent(container, BASIC_CONTAINER); + return api.containerApiInRegion(region.getId()).createIfAbsent(container, BASIC_CONTAINER); } private static final org.jclouds.openstack.swift.v1.options.CreateContainerOptions BASIC_CONTAINER = new org.jclouds.openstack.swift.v1.options.CreateContainerOptions(); @@ -124,22 +141,35 @@ public class RegionScopedSwiftBlobStore extends BaseBlobStore { .anybodyRead(); @Override - public PageSet<? extends StorageMetadata> list(String container, ListContainerOptions options) { - ObjectApi objectApi = api.objectApiInRegionForContainer(regionId(), container); + public PageSet<? extends StorageMetadata> list(String container) { + return list(container, ListContainerOptions.NONE); + } + + @Override + public PageSet<? extends StorageMetadata> list(final String container, ListContainerOptions options) { + ObjectApi objectApi = api.objectApiInRegionForContainer(region.getId(), container); ObjectList objects = objectApi.list(toListContainerOptions.apply(options)); if (objects == null) { containerCache.put(container, Optional.<Container> absent()); return new PageSetImpl<StorageMetadata>(ImmutableList.<StorageMetadata> of(), null); } else { containerCache.put(container, Optional.of(objects.container())); - List<MutableBlobMetadata> list = Lists.transform(objects, toBlobMetadata(container)); + List<? extends StorageMetadata> list = transform(objects, toBlobMetadata(container)); int limit = Optional.fromNullable(options.getMaxResults()).or(10000); String marker = list.size() == limit ? list.get(limit - 1).getName() : null; - PageSet<StorageMetadata> pageSet = new PageSetImpl<StorageMetadata>(list, marker); + // TODO: we should probably deprecate this option if (options.isDetailed()) { - return fetchMetadata.setContainerName(container).apply(pageSet); + list = transform(list, new Function<StorageMetadata, StorageMetadata>() { + @Override + public StorageMetadata apply(StorageMetadata input) { + if (input.getType() != StorageType.BLOB) { + return input; + } + return blobMetadata(container, input.getName()); + } + }); } - return pageSet; + return new PageSetImpl<StorageMetadata>(list, marker); } } @@ -158,13 +188,13 @@ public class RegionScopedSwiftBlobStore extends BaseBlobStore { if (options.isMultipart()) { throw new UnsupportedOperationException(); } - ObjectApi objectApi = api.objectApiInRegionForContainer(regionId(), container); + ObjectApi objectApi = api.objectApiInRegionForContainer(region.getId(), container); return objectApi.replace(blob.getMetadata().getName(), blob.getPayload(), blob.getMetadata().getUserMetadata()); } @Override public BlobMetadata blobMetadata(String container, String name) { - SwiftObject object = api.objectApiInRegionForContainer(regionId(), container).head(name); + SwiftObject object = api.objectApiInRegionForContainer(region.getId(), container).head(name); if (object == null) { return null; } @@ -172,8 +202,13 @@ public class RegionScopedSwiftBlobStore extends BaseBlobStore { } @Override + public Blob getBlob(String container, String key) { + return getBlob(container, key, GetOptions.NONE); + } + + @Override public Blob getBlob(String container, String name, GetOptions options) { - ObjectApi objectApi = api.objectApiInRegionForContainer(regionId(), container); + ObjectApi objectApi = api.objectApiInRegionForContainer(region.getId(), container); SwiftObject object = objectApi.get(name, toGetOptions.apply(options)); if (object == null) { return null; @@ -185,24 +220,80 @@ public class RegionScopedSwiftBlobStore extends BaseBlobStore { @Override public void removeBlob(String container, String name) { - api.objectApiInRegionForContainer(regionId(), container).delete(name); + api.objectApiInRegionForContainer(region.getId(), container).delete(name); + } + + @Override + public BlobStoreContext getContext() { + return context; } @Override - protected boolean deleteAndVerifyContainerGone(String container) { - api.containerApiInRegion(regionId()).deleteIfEmpty(container); + public BlobBuilder blobBuilder(String name) { + return new BlobBuilderImpl().name(name); + } + + @Override + public boolean directoryExists(String containerName, String directory) { + return api.objectApiInRegionForContainer(region.getId(), containerName) // + .head(directory) != null; + } + + @Override + public void createDirectory(String containerName, String directory) { + api.objectApiInRegionForContainer(region.getId(), containerName) // + .replace(directory, directoryPayload, ImmutableMap.<String, String> of()); + } + + private final Payload directoryPayload = new ByteArrayPayload(new byte[] {}) { + { + getContentMetadata().setContentType("application/directory"); + } + }; + + @Override + public void deleteDirectory(String containerName, String directory) { + api.objectApiInRegionForContainer(region.getId(), containerName).delete(directory); + } + + @Override + public long countBlobs(String containerName) { + Container container = api.containerApiInRegion(region.getId()).get(containerName); + // undefined if container doesn't exist, so default to zero + return container != null ? container.objectCount() : 0; + } + + @Override + public void clearContainer(String containerName) { + clearContainer(containerName, recursive()); + } + + @Override + public void clearContainer(String containerName, ListContainerOptions options) { + // this could be implemented to use bulk delete + clearList.execute(containerName, options); + } + + @Override + public void deleteContainer(String container) { + clearContainer(container, recursive()); + api.containerApiInRegion(region.getId()).deleteIfEmpty(container); containerCache.invalidate(container); - return true; } protected final LoadingCache<String, Optional<Container>> containerCache = CacheBuilder.newBuilder().build( new CacheLoader<String, Optional<Container>>() { public Optional<Container> load(String container) { - return Optional.fromNullable(api.containerApiInRegion(regionId()).get(container)); + return Optional.fromNullable(api.containerApiInRegion(region.getId()).get(container)); } }); protected Function<SwiftObject, MutableBlobMetadata> toBlobMetadata(String container) { return new ToBlobMetadata(containerCache.getUnchecked(container).get()); } + + @Override + public long countBlobs(String containerName, ListContainerOptions options) { + throw new UnsupportedOperationException(); + } } http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/44021fc9/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedTemporaryUrlBlobSigner.java ---------------------------------------------------------------------- diff --git a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedTemporaryUrlBlobSigner.java b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedTemporaryUrlBlobSigner.java index 81dce15..78dc63f 100644 --- a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedTemporaryUrlBlobSigner.java +++ b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedTemporaryUrlBlobSigner.java @@ -17,8 +17,10 @@ package org.jclouds.openstack.swift.v1.blobstore; import static com.google.common.base.Preconditions.checkNotNull; +import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL; import java.net.URI; +import java.util.Map; import java.util.concurrent.TimeUnit; import javax.inject.Provider; @@ -31,31 +33,36 @@ import org.jclouds.http.HttpRequest; import org.jclouds.http.Uris; import org.jclouds.http.options.GetOptions; import org.jclouds.location.Region; +import org.jclouds.openstack.swift.v1.SwiftApi; import org.jclouds.openstack.swift.v1.TemporaryUrlSigner; import com.google.common.base.Supplier; import com.google.inject.Inject; +import com.google.inject.assistedinject.Assisted; +import com.google.inject.name.Named; /** * Uses {@link TemporaryUrlSigner} to sign requests for access to blobs. If no * interval is supplied, it defaults to a year. */ public class RegionScopedTemporaryUrlBlobSigner implements BlobRequestSigner { - private static final long YEAR = TimeUnit.DAYS.toSeconds(365); - private final BlobToHttpGetOptions toGetOptions = new BlobToHttpGetOptions(); - - private final Supplier<TemporaryUrlSigner> signer; - private final Supplier<URI> storageUrl; - private final Provider<Long> timestamp; @Inject - protected RegionScopedTemporaryUrlBlobSigner(Supplier<TemporaryUrlSigner> signer, @Region Supplier<URI> storageUrl, - @TimeStamp Provider<Long> timestamp) { - this.signer = signer; - this.storageUrl = storageUrl; + protected RegionScopedTemporaryUrlBlobSigner(@Region Supplier<Map<String, Supplier<URI>>> regionToUris, + @Named(PROPERTY_SESSION_INTERVAL) long seconds, @TimeStamp Provider<Long> timestamp, SwiftApi api, + @Assisted String regionId) { + checkNotNull(regionId, "regionId"); this.timestamp = timestamp; + this.signer = TemporaryUrlSigner.checkApiEvery(api.accountApiInRegion(regionId), seconds); + this.storageUrl = regionToUris.get().get(regionId).get(); } + private static final long YEAR = TimeUnit.DAYS.toSeconds(365); + private final BlobToHttpGetOptions toGetOptions = new BlobToHttpGetOptions(); + private final Provider<Long> timestamp; + private final TemporaryUrlSigner signer; + private final URI storageUrl; + @Override public HttpRequest signGetBlob(String container, String name) { return signGetBlob(container, name, YEAR); @@ -89,8 +96,8 @@ public class RegionScopedTemporaryUrlBlobSigner implements BlobRequestSigner { private HttpRequest sign(String method, String container, String name, GetOptions options, long expires) { checkNotNull(container, "container"); checkNotNull(name, "name"); - URI url = Uris.uriBuilder(storageUrl.get()).appendPath(container).appendPath(name).build(); - String signature = signer.get().sign(method, url.getPath(), expires); + URI url = Uris.uriBuilder(storageUrl).appendPath(container).appendPath(name).build(); + String signature = signer.sign(method, url.getPath(), expires); return HttpRequest.builder() .method(method) .endpoint(url) http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/44021fc9/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SignUsingTemporaryUrls.java ---------------------------------------------------------------------- diff --git a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SignUsingTemporaryUrls.java b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SignUsingTemporaryUrls.java index 91c753c..3a551a1 100644 --- a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SignUsingTemporaryUrls.java +++ b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SignUsingTemporaryUrls.java @@ -16,63 +16,52 @@ */ package org.jclouds.openstack.swift.v1.blobstore.config; -import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL; - -import java.net.URI; -import java.util.Map; - -import javax.inject.Singleton; +import javax.inject.Inject; import org.jclouds.blobstore.BlobRequestSigner; import org.jclouds.date.TimeStamp; -import org.jclouds.location.Region; -import org.jclouds.openstack.swift.v1.SwiftApi; -import org.jclouds.openstack.swift.v1.TemporaryUrlSigner; import org.jclouds.openstack.swift.v1.blobstore.RegionScopedTemporaryUrlBlobSigner; -import com.google.common.base.Supplier; -import com.google.common.base.Suppliers; +import com.google.common.base.Function; +import com.google.common.collect.ForwardingObject; import com.google.inject.AbstractModule; import com.google.inject.Provides; -import com.google.inject.name.Named; +import com.google.inject.assistedinject.FactoryModuleBuilder; public class SignUsingTemporaryUrls extends AbstractModule { @Override protected void configure() { - bind(BlobRequestSigner.class).to(RegionScopedTemporaryUrlBlobSigner.class); + install(new FactoryModuleBuilder().build(Factory.class)); } - @Provides - @TimeStamp - protected Long unixEpochTimestamp() { - return System.currentTimeMillis() / 1000; + static interface Factory { + RegionScopedTemporaryUrlBlobSigner create(String in); } @Provides - @Singleton - Supplier<TemporaryUrlSigner> regionScopedTemporaryUrlSigner(final SwiftApi api, - @Region final Supplier<String> defaultRegion, @Named(PROPERTY_SESSION_INTERVAL) final long seconds) { - return Suppliers.memoize(new Supplier<TemporaryUrlSigner>() { + Function<String, BlobRequestSigner> blobRequestSigner(FactoryFunction in) { + return in; + } - @Override - public TemporaryUrlSigner get() { - return TemporaryUrlSigner.checkApiEvery(api.accountApiInRegion(defaultRegion.get()), seconds); - } - }); + static class FactoryFunction extends ForwardingObject implements Function<String, BlobRequestSigner> { + @Inject + Factory delegate; + + @Override + protected Factory delegate() { + return delegate; + } + + @Override + public BlobRequestSigner apply(String in) { + return delegate.create(in); + } } @Provides - @Singleton - @Region - Supplier<URI> storageUrl(@Region final Supplier<String> defaultRegion, - @Region final Supplier<Map<String, Supplier<URI>>> regionToUris) { - return Suppliers.memoize(new Supplier<URI>() { - - @Override - public URI get() { - return regionToUris.get().get(defaultRegion.get()).get(); - } - }); + @TimeStamp + protected Long unixEpochTimestamp() { + return System.currentTimeMillis() / 1000; } } http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/44021fc9/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SwiftBlobStoreContextModule.java ---------------------------------------------------------------------- diff --git a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SwiftBlobStoreContextModule.java b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SwiftBlobStoreContextModule.java index 4452cdd..efd2166 100644 --- a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SwiftBlobStoreContextModule.java +++ b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SwiftBlobStoreContextModule.java @@ -16,19 +16,48 @@ */ package org.jclouds.openstack.swift.v1.blobstore.config; -import org.jclouds.blobstore.AsyncBlobStore; +import javax.inject.Inject; + import org.jclouds.blobstore.BlobStore; -import org.jclouds.blobstore.attr.ConsistencyModel; +import org.jclouds.blobstore.BlobStoreContext; +import org.jclouds.openstack.swift.v1.blobstore.RegionScopedBlobStoreContext; import org.jclouds.openstack.swift.v1.blobstore.RegionScopedSwiftBlobStore; -import org.jclouds.openstack.swift.v1.blobstore.internal.SubmissionAsyncBlobStore; +import com.google.common.base.Function; +import com.google.common.collect.ForwardingObject; import com.google.inject.AbstractModule; +import com.google.inject.Provides; +import com.google.inject.assistedinject.FactoryModuleBuilder; public class SwiftBlobStoreContextModule extends AbstractModule { + @Override protected void configure() { - bind(ConsistencyModel.class).toInstance(ConsistencyModel.STRICT); - bind(AsyncBlobStore.class).to(SubmissionAsyncBlobStore.class); - bind(BlobStore.class).to(RegionScopedSwiftBlobStore.class); + bind(BlobStoreContext.class).to(RegionScopedBlobStoreContext.class); + install(new FactoryModuleBuilder().build(Factory.class)); + } + + static interface Factory { + RegionScopedSwiftBlobStore create(String in); + } + + @Provides + Function<String, BlobStore> blobStore(FactoryFunction in) { + return in; + } + + static class FactoryFunction extends ForwardingObject implements Function<String, BlobStore> { + @Inject + Factory delegate; + + @Override + protected Factory delegate() { + return delegate; + } + + @Override + public BlobStore apply(String in) { + return delegate.create(in); + } } } http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/44021fc9/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToResourceMetadata.java ---------------------------------------------------------------------- diff --git a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToResourceMetadata.java b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToResourceMetadata.java index 6d12ce8..0384388 100644 --- a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToResourceMetadata.java +++ b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToResourceMetadata.java @@ -24,12 +24,11 @@ import org.jclouds.domain.Location; import org.jclouds.openstack.swift.v1.domain.Container; import com.google.common.base.Function; -import com.google.common.base.Supplier; public class ToResourceMetadata implements Function<Container, StorageMetadata> { - private Supplier<Location> region; + private Location region; - public ToResourceMetadata(Supplier<Location> region) { + public ToResourceMetadata(Location region) { this.region = region; } @@ -37,7 +36,7 @@ public class ToResourceMetadata implements Function<Container, StorageMetadata> public StorageMetadata apply(Container from) { MutableStorageMetadata to = new MutableStorageMetadataImpl(); to.setName(from.name()); - to.setLocation(region.get()); + to.setLocation(region); to.setType(StorageType.CONTAINER); to.setUserMetadata(from.metadata()); return to; http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/44021fc9/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/internal/SubmissionAsyncBlobStore.java ---------------------------------------------------------------------- diff --git a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/internal/SubmissionAsyncBlobStore.java b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/internal/SubmissionAsyncBlobStore.java index 1d083b2..3710270 100644 --- a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/internal/SubmissionAsyncBlobStore.java +++ b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/internal/SubmissionAsyncBlobStore.java @@ -56,7 +56,7 @@ public class SubmissionAsyncBlobStore extends ForwardingObject implements AsyncB private final ListeningExecutorService executor; @Inject - SubmissionAsyncBlobStore(BlobStore blobstore, @Named(PROPERTY_USER_THREADS) ListeningExecutorService executor) { + public SubmissionAsyncBlobStore(BlobStore blobstore, @Named(PROPERTY_USER_THREADS) ListeningExecutorService executor) { this.blobstore = checkNotNull(blobstore, "blobstore"); this.executor = checkNotNull(executor, "executor"); } http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/44021fc9/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContextLiveTest.java ---------------------------------------------------------------------- diff --git a/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContextLiveTest.java b/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContextLiveTest.java new file mode 100644 index 0000000..75f273e --- /dev/null +++ b/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContextLiveTest.java @@ -0,0 +1,95 @@ +/* + * 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.openstack.swift.v1.blobstore; + +import static org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties.CREDENTIAL_TYPE; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; + +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.ExecutionException; + +import org.jclouds.blobstore.BlobStore; +import org.jclouds.blobstore.domain.PageSet; +import org.jclouds.blobstore.domain.StorageMetadata; +import org.jclouds.blobstore.integration.internal.BaseBlobStoreIntegrationTest; +import org.jclouds.domain.Location; +import org.jclouds.http.HttpRequest; +import org.testng.annotations.Test; + +import com.google.common.collect.Iterables; + +@Test(groups = "live") +public class RegionScopedBlobStoreContextLiveTest extends BaseBlobStoreIntegrationTest { + + public RegionScopedBlobStoreContextLiveTest() { + provider = "openstack-swift"; + } + + @Override + protected Properties setupProperties() { + Properties props = super.setupProperties(); + setIfTestSystemPropertyPresent(props, CREDENTIAL_TYPE); + return props; + } + + @Test + public void regionsAreNotEmpty() { + assertFalse(RegionScopedBlobStoreContext.class.cast(view).configuredRegions().isEmpty()); + } + + @Test + public void locationsMatch() { + RegionScopedBlobStoreContext ctx = RegionScopedBlobStoreContext.class.cast(view); + for (String regionId : ctx.configuredRegions()) { + Set<? extends Location> locations = ctx.blobStoreInRegion(regionId).listAssignableLocations(); + assertEquals(locations.size(), 1, "expected one region " + regionId + " " + locations); + Location location = locations.iterator().next(); + assertEquals(location.getId(), regionId, "region id " + regionId + " didn't match getId(): " + location); + } + } + + @Test + public void tryList() throws InterruptedException, ExecutionException { + RegionScopedBlobStoreContext ctx = RegionScopedBlobStoreContext.class.cast(view); + for (String regionId : ctx.configuredRegions()) { + assertEquals(ctx.asyncBlobStoreInRegion(regionId).list().get(), ctx.blobStoreInRegion(regionId).list()); + } + } + + @Test + public void trySign() throws InterruptedException, ExecutionException { + RegionScopedBlobStoreContext ctx = RegionScopedBlobStoreContext.class.cast(view); + for (String regionId : ctx.configuredRegions()) { + BlobStore region = ctx.blobStoreInRegion(regionId); + PageSet<? extends StorageMetadata> containers = region.list(); + if (containers.isEmpty()) { + continue; + } + String containerName = Iterables.getLast(containers).getName(); + PageSet<? extends StorageMetadata> blobs = region.list(containerName); + if (blobs.isEmpty()) { + continue; + } + String blobName = Iterables.getLast(blobs).getName(); + HttpRequest request = ctx.signerInRegion(regionId).signGetBlob(containerName, blobName); + assertNotNull(request, "regionId=" + regionId + ", container=" + containerName + ", blob=" + blobName); + } + } +}
