From 4b769822619deca38ed5c955d167eeddf6307321 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Thu, 12 Aug 2010 18:53:51 -0700 Subject: [PATCH] Issue 191: added in-memory chef-client; use provider: transientchef --- .../blobstore/TransientAsyncBlobStore.java | 84 +++-- chef/core/pom.xml | 10 +- .../src/main/clojure/org/jclouds/chef.clj | 50 ++- .../org/jclouds/chef/ChefContextFactory.java | 65 +++- .../chef/test/TransientChefAsyncClient.java | 346 ++++++++++++++++++ .../chef/test/TransientChefClient.java | 39 ++ .../test/TransientChefContextBuilder.java | 43 +++ .../config/TransientChefClientModule.java | 63 ++++ .../test/clojure/org/jclouds/chef_test.clj | 69 ++++ .../jclouds/chef/BaseChefClientLiveTest.java | 2 +- .../TransientChefClientIntegrationTest.java | 327 +++++++++++++++++ core/src/main/resources/rest.properties | 3 + 12 files changed, 1051 insertions(+), 50 deletions(-) create mode 100644 chef/core/src/main/java/org/jclouds/chef/test/TransientChefAsyncClient.java create mode 100644 chef/core/src/main/java/org/jclouds/chef/test/TransientChefClient.java create mode 100644 chef/core/src/main/java/org/jclouds/chef/test/TransientChefContextBuilder.java create mode 100644 chef/core/src/main/java/org/jclouds/chef/test/config/TransientChefClientModule.java create mode 100644 chef/core/src/test/clojure/org/jclouds/chef_test.clj create mode 100644 chef/core/src/test/java/org/jclouds/chef/test/TransientChefClientIntegrationTest.java diff --git a/blobstore/src/main/java/org/jclouds/blobstore/TransientAsyncBlobStore.java b/blobstore/src/main/java/org/jclouds/blobstore/TransientAsyncBlobStore.java index bd577d78ce..9d5b2b7605 100755 --- a/blobstore/src/main/java/org/jclouds/blobstore/TransientAsyncBlobStore.java +++ b/blobstore/src/main/java/org/jclouds/blobstore/TransientAsyncBlobStore.java @@ -45,6 +45,7 @@ import java.io.ObjectOutput; import java.io.ObjectOutputStream; import java.net.URI; import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; @@ -274,6 +275,13 @@ public class TransientAsyncBlobStore extends BaseAsyncBlobStore { return immediateFuture(null); } + public ListenableFuture removeBlobAndReturnOld(String container, String key) { + if (getContainerToBlobs().containsKey(container)) { + return immediateFuture(getContainerToBlobs().get(container).remove(key)); + } + return immediateFuture(null); + } + /** * {@inheritDoc} */ @@ -346,6 +354,20 @@ public class TransientAsyncBlobStore extends BaseAsyncBlobStore { return immediateFuture(getContainerToBlobs().containsKey(name)); } + /** + * throws IllegalStateException if the container already exists + */ + public ListenableFuture createContainerInLocationIfAbsent(final Location location, final String name) { + ConcurrentMap container = getContainerToBlobs().putIfAbsent(name, + new ConcurrentHashMap()); + if (container == null) { + getContainerToLocation().put(name, location != null ? location : defaultLocation); + return immediateFuture((Void) null); + } else { + return Futures.immediateFailedFuture(new IllegalStateException("container " + name + " already exists")); + } + } + public String getFirstQueryOrNull(String string, @Nullable HttpRequestOptions options) { if (options == null) return null; @@ -454,33 +476,54 @@ public class TransientAsyncBlobStore extends BaseAsyncBlobStore { * {@inheritDoc} */ @Override - public ListenableFuture putBlob(String containerName, Blob object) { - Map container = getContainerToBlobs().get(containerName); + public ListenableFuture putBlob(String containerName, Blob in) { + ConcurrentMap container = getContainerToBlobs().get(containerName); if (container == null) { - new RuntimeException("containerName not found: " + containerName); + new IllegalStateException("containerName not found: " + containerName); } - ByteArrayPayload payload = (object.getPayload() instanceof ByteArrayPayload) ? ByteArrayPayload.class.cast(object + Blob blob = createUpdatedCopyOfBlob(in); + + container.put(blob.getMetadata().getName(), blob); + + return immediateFuture(Iterables.getOnlyElement(blob.getAllHeaders().get(HttpHeaders.ETAG))); + } + + public ListenableFuture putBlobAndReturnOld(String containerName, Blob in) { + ConcurrentMap container = getContainerToBlobs().get(containerName); + if (container == null) { + new IllegalStateException("containerName not found: " + containerName); + } + + Blob blob = createUpdatedCopyOfBlob(in); + + Blob old = container.put(blob.getMetadata().getName(), blob); + + return immediateFuture(old); + } + + protected Blob createUpdatedCopyOfBlob(Blob in) { + ByteArrayPayload payload = (in.getPayload() instanceof ByteArrayPayload) ? ByteArrayPayload.class.cast(in .getPayload()) : null; if (payload == null) - payload = (object.getPayload() instanceof DelegatingPayload) ? (DelegatingPayload.class.cast( - object.getPayload()).getDelegate() instanceof ByteArrayPayload) ? ByteArrayPayload.class - .cast(DelegatingPayload.class.cast(object.getPayload()).getDelegate()) : null : null; + payload = (in.getPayload() instanceof DelegatingPayload) ? (DelegatingPayload.class.cast(in.getPayload()) + .getDelegate() instanceof ByteArrayPayload) ? ByteArrayPayload.class.cast(DelegatingPayload.class.cast( + in.getPayload()).getDelegate()) : null : null; try { if (payload == null || !(payload instanceof ByteArrayPayload)) { - String oldContentType = object.getPayload().getContentType(); + String oldContentType = in.getPayload().getContentType(); ByteArrayOutputStream out = new ByteArrayOutputStream(); - object.getPayload().writeTo(out); + in.getPayload().writeTo(out); payload = (ByteArrayPayload) Payloads.calculateMD5(Payloads.newPayload(out.toByteArray())); payload.setContentType(oldContentType); } else { if (payload.getContentMD5() == null) - Payloads.calculateMD5(object, crypto.md5()); + Payloads.calculateMD5(in, crypto.md5()); } } catch (IOException e) { Throwables.propagate(e); } - Blob blob = blobFactory.create(copy(object.getMetadata())); + Blob blob = blobFactory.create(copy(in.getMetadata())); blob.setPayload(payload); blob.getMetadata().setLastModified(new Date()); blob.getMetadata().setSize(payload.getContentLength()); @@ -489,18 +532,17 @@ public class TransientAsyncBlobStore extends BaseAsyncBlobStore { String eTag = CryptoStreams.hex(payload.getContentMD5()); blob.getMetadata().setETag(eTag); - container.put(blob.getMetadata().getName(), blob); - // Set HTTP headers to match metadata - blob.getAllHeaders().put(HttpHeaders.LAST_MODIFIED, - dateService.rfc822DateFormat(blob.getMetadata().getLastModified())); - blob.getAllHeaders().put(HttpHeaders.ETAG, eTag); - blob.getAllHeaders().put(HttpHeaders.CONTENT_TYPE, payload.getContentType()); - blob.getAllHeaders().put(HttpHeaders.CONTENT_LENGTH, payload.getContentLength() + ""); - blob.getAllHeaders().put("Content-MD5", CryptoStreams.base64(payload.getContentMD5())); + blob.getAllHeaders().replaceValues(HttpHeaders.LAST_MODIFIED, + Collections.singleton(dateService.rfc822DateFormat(blob.getMetadata().getLastModified()))); + blob.getAllHeaders().replaceValues(HttpHeaders.ETAG, Collections.singleton(eTag)); + blob.getAllHeaders().replaceValues(HttpHeaders.CONTENT_TYPE, Collections.singleton(payload.getContentType())); + blob.getAllHeaders().replaceValues(HttpHeaders.CONTENT_LENGTH, + Collections.singleton(payload.getContentLength() + "")); + blob.getAllHeaders().replaceValues("Content-MD5", + Collections.singleton(CryptoStreams.base64(payload.getContentMD5()))); blob.getAllHeaders().putAll(Multimaps.forMap(blob.getMetadata().getUserMetadata())); - - return immediateFuture(eTag); + return blob; } /** diff --git a/chef/core/pom.xml b/chef/core/pom.xml index 5f368957d8..e0bf76592f 100644 --- a/chef/core/pom.xml +++ b/chef/core/pom.xml @@ -1,7 +1,6 @@ + + ${project.groupId} + jclouds-blobstore + ${project.version} + true + diff --git a/chef/core/src/main/clojure/org/jclouds/chef.clj b/chef/core/src/main/clojure/org/jclouds/chef.clj index 4de9b2a67c..1f0f084895 100644 --- a/chef/core/src/main/clojure/org/jclouds/chef.clj +++ b/chef/core/src/main/clojure/org/jclouds/chef.clj @@ -30,12 +30,16 @@ which is basically Chef Server as a Service. ;; load the rsa key from ~/.chef/CLIENT_NAME.pem (def credential (load-pem client)) -(def chef (chef-service client credential :chef.endpoint \"https://api.opscode.com/organizations/YOUR_ORG\")) +;; create a connection to the opscode platform +(def chef (chef-service \"chef\" client credential :chef.endpoint \"https://api.opscode.com/organizations/YOUR_ORG\")) (with-chef-service [chef] (create-databag \"cluster-config\") (update-databag-item \"cluster-config\" {:id \"master\" :name \"myhost.com\"})) +;; note that you can create your chef connection like this to do in-memory testing +(def chef (chef-service \"transientchef\" \"\" \"\")) + See http://code.google.com/p/jclouds for details."} org.jclouds.chef (:use [org.jclouds.core]) @@ -57,18 +61,26 @@ See http://code.google.com/p/jclouds for details."} ([#^String identity] (slurp (str (. System getProperty "user.home") "/.chef/" identity ".pem")))) +;; TODO find a way to pass the chef provider by default + (defn chef-service - "Create a logged in context." - ([#^String identity #^String credential & options] - (let [module-keys (set (keys module-lookup)) - ext-modules (filter #(module-keys %) options) - opts (apply hash-map (filter #(not (module-keys %)) options))] - (.. (ChefContextFactory.) - (createContext identity credential - (apply modules (concat ext-modules (opts :extensions))) - (reduce #(do (.put %1 (name (first %2)) (second %2)) %1) - (Properties.) (dissoc opts :extensions))) - (getChefService))))) + "Create a logged in context to a chef server. + +provider \"chef\" is a remote connection, and you can pass the option + :chef.endpoint \"https://url\" to override the endpoint + +provider \"transientchef\" is for in-memory when you are looking to do +unit testing" + ([#^String provider #^String identity #^String credential & options] + (let [module-keys (set (keys module-lookup)) + ext-modules (filter #(module-keys %) options) + opts (apply hash-map (filter #(not (module-keys %)) options))] + (.. (ChefContextFactory.) + (createContext provider identity credential + (apply modules (concat ext-modules (opts :extensions))) + (reduce #(do (.put %1 (name (first %2)) (second %2)) %1) + (Properties.) (dissoc opts :extensions))) + (getChefService))))) (defn chef-context "Returns a chef context from a chef service." @@ -125,6 +137,13 @@ See http://code.google.com/p/jclouds for details."} ([#^ChefService chef] (seq (.listDatabags (as-chef-api chef))))) +(defn databag-exists? + "Predicate to check presence of a databag" + ([databag-name] + (databag-exists? databag-name *chef*)) + ([databag-name #^ChefService chef] + (.databagExists (as-chef-api chef) databag-name))) + (defn delete-databag "Delete a data bag, including its items" ([databag] @@ -146,6 +165,13 @@ See http://code.google.com/p/jclouds for details."} ([databag chef] (seq (.listDatabagItems (as-chef-api chef) databag)))) +(defn databag-item-exists? + "Predicate to check presence of a databag item" + ([databag-name item-id] + (databag-item-exists? databag-name item-id *chef*)) + ([databag-name item-id #^ChefService chef] + (.databagExists (as-chef-api chef) databag-name item-id))) + (defn databag-item "Get an item from the data bag" ([databag item-id] diff --git a/chef/core/src/main/java/org/jclouds/chef/ChefContextFactory.java b/chef/core/src/main/java/org/jclouds/chef/ChefContextFactory.java index 9dfcafd183..919437c1bc 100644 --- a/chef/core/src/main/java/org/jclouds/chef/ChefContextFactory.java +++ b/chef/core/src/main/java/org/jclouds/chef/ChefContextFactory.java @@ -75,50 +75,87 @@ public class ChefContextFactory { } /** - * @see RestContextFactory#createContextBuilder(String, String) + * @see #createContext(String, String, String) */ public ChefContext createContext(String identity, String credential) { - RestContextBuilder builder = RestContextBuilder.class.cast(contextFactory.createContextBuilder("chef", + return createContext("chef", identity, credential); + } + + /** + * @see RestContextFactory#createContextBuilder(String, String, String) + */ + public ChefContext createContext(String provider, String identity, String credential) { + RestContextBuilder builder = RestContextBuilder.class.cast(contextFactory.createContextBuilder(provider, identity, credential)); return buildContextUnwrappingExceptions(builder); } /** - * @see RestContextFactory#createContextBuilder(Properties) + * @see #createContext(String, Properties) */ public ChefContext createContext(Properties overrides) { - RestContextBuilder builder = RestContextBuilder.class.cast(contextFactory.createContextBuilder("chef", + return createContext("chef", overrides); + } + + /** + * @see RestContextFactory#createContextBuilder(String, Properties) + */ + public ChefContext createContext(String provider, Properties overrides) { + RestContextBuilder builder = RestContextBuilder.class.cast(contextFactory.createContextBuilder(provider, overrides)); return buildContextUnwrappingExceptions(builder); } /** - * @see RestContextFactory#createContextBuilder(Iterable) + * @see #createContext(String, Iterable, Properties) */ public ChefContext createContext(Iterable modules, Properties overrides) { - RestContextBuilder builder = RestContextBuilder.class.cast(contextFactory.createContextBuilder("chef", - modules, overrides)); - return buildContextUnwrappingExceptions(builder); - + return createContext("chef", modules, overrides); } /** - * @see RestContextFactory#createContextBuilder(String,String, Iterable) + * @see RestContextFactory#createContextBuilder(String, Iterable, Properties) + */ + public ChefContext createContext(String provider, Iterable modules, Properties overrides) { + RestContextBuilder builder = RestContextBuilder.class.cast(contextFactory.createContextBuilder(provider, + modules, overrides)); + return buildContextUnwrappingExceptions(builder); + } + + /** + * @see #createContext(String,String,String,Iterable) */ public ChefContext createContext(@Nullable String identity, @Nullable String credential, Iterable modules) { - RestContextBuilder builder = RestContextBuilder.class.cast(contextFactory.createContextBuilder("chef", + return createContext("chef", identity, credential, modules); + } + + /** + * @see RestContextFactory#createContextBuilder(String,String String, + * Iterable) + */ + public ChefContext createContext(String provider, @Nullable String identity, @Nullable String credential, + Iterable modules) { + RestContextBuilder builder = RestContextBuilder.class.cast(contextFactory.createContextBuilder(provider, identity, credential, modules)); return buildContextUnwrappingExceptions(builder); } /** - * @see RestContextFactory#createContextBuilder(String,String, Iterable, - * Properties) + * @see #createContext(String,String, String, Iterable, Properties) */ public ChefContext createContext(@Nullable String identity, @Nullable String credential, Iterable modules, Properties overrides) { - RestContextBuilder builder = RestContextBuilder.class.cast(contextFactory.createContextBuilder("chef", + return createContext("chef", identity, credential, modules, overrides); + } + + /** + * @see RestContextFactory#createContextBuilder(String,String,String, + * Iterable, Properties) + */ + public ChefContext createContext(String provider, @Nullable String identity, @Nullable String credential, + Iterable modules, Properties overrides) { + RestContextBuilder builder = RestContextBuilder.class.cast(contextFactory.createContextBuilder(provider, identity, credential, modules, overrides)); return buildContextUnwrappingExceptions(builder); } diff --git a/chef/core/src/main/java/org/jclouds/chef/test/TransientChefAsyncClient.java b/chef/core/src/main/java/org/jclouds/chef/test/TransientChefAsyncClient.java new file mode 100644 index 0000000000..e1cde03148 --- /dev/null +++ b/chef/core/src/main/java/org/jclouds/chef/test/TransientChefAsyncClient.java @@ -0,0 +1,346 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.chef.test; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Throwables.propagate; +import static com.google.common.collect.Iterables.transform; +import static com.google.common.collect.Sets.newLinkedHashSet; +import static org.jclouds.concurrent.Futures.compose; + +import java.io.IOException; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutorService; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import org.jclouds.Constants; +import org.jclouds.blobstore.TransientAsyncBlobStore; +import org.jclouds.blobstore.domain.Blob; +import org.jclouds.blobstore.domain.PageSet; +import org.jclouds.blobstore.domain.StorageMetadata; +import org.jclouds.chef.ChefAsyncClient; +import org.jclouds.chef.domain.Client; +import org.jclouds.chef.domain.CookbookVersion; +import org.jclouds.chef.domain.DatabagItem; +import org.jclouds.chef.domain.Node; +import org.jclouds.chef.domain.Role; +import org.jclouds.chef.domain.Sandbox; +import org.jclouds.chef.domain.SearchResult; +import org.jclouds.chef.domain.UploadSandbox; +import org.jclouds.util.Utils; + +import com.google.common.base.Function; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; + +/** + * In-memory chef simulator. + * + * @author Adrian Cole + */ + +public class TransientChefAsyncClient implements ChefAsyncClient { + @Singleton + private static class StorageMetadataToName implements Function, Set> { + @Override + public Set apply(PageSet from) { + return newLinkedHashSet(transform(from, new Function() { + + @Override + public String apply(StorageMetadata from) { + return from.getName(); + } + })); + } + } + + @Singleton + private static class BlobToDatabagItem implements Function { + @Override + public DatabagItem apply(Blob from) { + try { + return from == null ? null : new DatabagItem(from.getMetadata().getName(), Utils.toStringAndClose(from + .getPayload().getInput())); + } catch (IOException e) { + propagate(e); + return null; + } + } + } + + private final TransientAsyncBlobStore databags; + private final ExecutorService executor; + private final BlobToDatabagItem blobToDatabagItem; + private final StorageMetadataToName storageMetadataToName; + + @Inject + TransientChefAsyncClient(@Named("databags") TransientAsyncBlobStore databags, + StorageMetadataToName storageMetadataToName, BlobToDatabagItem blobToDatabagItem, + @Named(Constants.PROPERTY_USER_THREADS) ExecutorService executor) { + this.databags = checkNotNull(databags, "databags"); + this.storageMetadataToName = checkNotNull(storageMetadataToName, "storageMetadataToName"); + this.blobToDatabagItem = checkNotNull(blobToDatabagItem, "blobToDatabagItem"); + this.executor = checkNotNull(executor, "executor"); + } + + @Override + public ListenableFuture clientExists(String clientname) { + // TODO Auto-generated method stub + return null; + } + + @Override + public ListenableFuture commitSandbox(String id, boolean isCompleted) { + // TODO Auto-generated method stub + return null; + } + + @Override + public ListenableFuture createClient(String clientname) { + // TODO Auto-generated method stub + return null; + } + + @Override + public ListenableFuture createDatabag(String databagName) { + return databags.createContainerInLocationIfAbsent(null, databagName); + } + + @Override + public ListenableFuture createDatabagItem(String databagName, DatabagItem databagItem) { + Blob blob = databags.newBlob(databagItem.getId()); + blob.setPayload(databagItem.toString()); + databags.putBlobAndReturnOld(databagName, blob); + return Futures.immediateFuture(databagItem); + } + + @Override + public ListenableFuture createNode(Node node) { + // TODO Auto-generated method stub + return null; + } + + @Override + public ListenableFuture createRole(Role role) { + // TODO Auto-generated method stub + return null; + } + + @Override + public ListenableFuture databagExists(String databagName) { + return databags.containerExists(databagName); + } + + @Override + public ListenableFuture databagItemExists(String databagName, String databagItemId) { + return databags.blobExists(databagName, databagItemId); + } + + @Override + public ListenableFuture deleteClient(String clientname) { + // TODO Auto-generated method stub + return null; + } + + @Override + public ListenableFuture deleteCookbook(String cookbookName, String version) { + // TODO Auto-generated method stub + return null; + } + + @Override + public ListenableFuture deleteDatabag(String databagName) { + return databags.deleteContainer(databagName); + } + + @Override + public ListenableFuture deleteDatabagItem(String databagName, String databagItemId) { + return compose(databags.removeBlobAndReturnOld(databagName, databagItemId), blobToDatabagItem, executor); + } + + @Override + public ListenableFuture deleteNode(String nodename) { + // TODO Auto-generated method stub + return null; + } + + @Override + public ListenableFuture deleteRole(String rolename) { + // TODO Auto-generated method stub + return null; + } + + @Override + public ListenableFuture generateKeyForClient(String clientname) { + // TODO Auto-generated method stub + return null; + } + + @Override + public ListenableFuture getClient(String clientname) { + // TODO Auto-generated method stub + return null; + } + + @Override + public ListenableFuture getCookbook(String cookbookName, String version) { + // TODO Auto-generated method stub + return null; + } + + @Override + public ListenableFuture getDatabagItem(String databagName, String databagItemId) { + return compose(databags.getBlob(databagName, databagItemId), blobToDatabagItem, executor); + } + + @Override + public ListenableFuture getNode(String nodename) { + // TODO Auto-generated method stub + return null; + } + + @Override + public ListenableFuture getRole(String rolename) { + // TODO Auto-generated method stub + return null; + } + + @Override + public ListenableFuture getUploadSandboxForChecksums(Set> md5s) { + // TODO Auto-generated method stub + return null; + } + + @Override + public ListenableFuture> getVersionsOfCookbook(String cookbookName) { + // TODO Auto-generated method stub + return null; + } + + @Override + public ListenableFuture> listClients() { + // TODO Auto-generated method stub + return null; + } + + @Override + public ListenableFuture> listCookbooks() { + // TODO Auto-generated method stub + return null; + } + + @Override + public ListenableFuture> listDatabagItems(String databagName) { + return compose(databags.list(databagName), storageMetadataToName, executor); + } + + @Override + public ListenableFuture> listDatabags() { + return compose(databags.list(), storageMetadataToName, executor); + } + + @Override + public ListenableFuture> listNodes() { + // TODO Auto-generated method stub + return null; + } + + @Override + public ListenableFuture> listRoles() { + // TODO Auto-generated method stub + return null; + } + + @Override + public ListenableFuture> listSearchIndexes() { + // TODO Auto-generated method stub + return null; + } + + @Override + public ListenableFuture nodeExists(String nodename) { + // TODO Auto-generated method stub + return null; + } + + @Override + public ListenableFuture roleExists(String rolename) { + // TODO Auto-generated method stub + return null; + } + + @Override + public ListenableFuture> searchClients() { + // TODO Auto-generated method stub + return null; + } + + @Override + public ListenableFuture> searchDatabag(String databagName) { + // TODO Auto-generated method stub + return null; + } + + @Override + public ListenableFuture> searchNodes() { + // TODO Auto-generated method stub + return null; + } + + @Override + public ListenableFuture> searchRoles() { + // TODO Auto-generated method stub + return null; + } + + @Override + public ListenableFuture updateCookbook(String cookbookName, String version, CookbookVersion cookbook) { + // TODO Auto-generated method stub + return null; + } + + @Override + public ListenableFuture updateDatabagItem(String databagName, DatabagItem item) { + return createDatabagItem(databagName, item); + } + + @Override + public ListenableFuture updateNode(Node node) { + // TODO Auto-generated method stub + return null; + } + + @Override + public ListenableFuture updateRole(Role role) { + // TODO Auto-generated method stub + return null; + } + + @Override + public ListenableFuture uploadContent(Set> md5s) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/chef/core/src/main/java/org/jclouds/chef/test/TransientChefClient.java b/chef/core/src/main/java/org/jclouds/chef/test/TransientChefClient.java new file mode 100644 index 0000000000..7aa44781b7 --- /dev/null +++ b/chef/core/src/main/java/org/jclouds/chef/test/TransientChefClient.java @@ -0,0 +1,39 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.chef.test; + +import java.util.concurrent.TimeUnit; + +import org.jclouds.chef.ChefAsyncClient; +import org.jclouds.chef.ChefClient; +import org.jclouds.concurrent.Timeout; + +/** + * In-memory chef simulator. + *

+ * + * @see ChefAsyncClient + * @see + * @author Adrian Cole + */ +@Timeout(duration = 30, timeUnit = TimeUnit.MILLISECONDS) +public interface TransientChefClient extends ChefClient { + +} diff --git a/chef/core/src/main/java/org/jclouds/chef/test/TransientChefContextBuilder.java b/chef/core/src/main/java/org/jclouds/chef/test/TransientChefContextBuilder.java new file mode 100644 index 0000000000..8a98f87f22 --- /dev/null +++ b/chef/core/src/main/java/org/jclouds/chef/test/TransientChefContextBuilder.java @@ -0,0 +1,43 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.chef.test; + +import java.util.List; +import java.util.Properties; + +import org.jclouds.chef.ChefContextBuilder; +import org.jclouds.chef.test.config.TransientChefClientModule; + +import com.google.inject.Module; + +/** + * @author Adrian Cole + */ +public class TransientChefContextBuilder extends ChefContextBuilder { + + public TransientChefContextBuilder(Properties props) { + super(props); + } + + @Override + protected void addClientModule(List modules) { + modules.add(new TransientChefClientModule()); + } +} \ No newline at end of file diff --git a/chef/core/src/main/java/org/jclouds/chef/test/config/TransientChefClientModule.java b/chef/core/src/main/java/org/jclouds/chef/test/config/TransientChefClientModule.java new file mode 100644 index 0000000000..3746661014 --- /dev/null +++ b/chef/core/src/main/java/org/jclouds/chef/test/config/TransientChefClientModule.java @@ -0,0 +1,63 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.chef.test.config; + +import javax.inject.Singleton; + +import org.jclouds.blobstore.TransientAsyncBlobStore; +import org.jclouds.chef.ChefAsyncClient; +import org.jclouds.chef.ChefClient; +import org.jclouds.chef.config.BaseChefRestClientModule; +import org.jclouds.chef.test.TransientChefAsyncClient; +import org.jclouds.chef.test.TransientChefClient; +import org.jclouds.rest.RestContextFactory; + +import com.google.inject.Provides; +import com.google.inject.name.Names; + +/** + * + * @author Adrian Cole + */ +public class TransientChefClientModule extends BaseChefRestClientModule { + + public TransientChefClientModule() { + super(TransientChefClient.class, ChefAsyncClient.class); + } + + @Override + protected void configure() { + bind(TransientAsyncBlobStore.class).annotatedWith(Names.named("databags")).toInstance( + new RestContextFactory().createContextBuilder("transient", "foo", "bar").buildInjector().getInstance( + TransientAsyncBlobStore.class)); + super.configure(); + } + + @Override + protected void bindAsyncClient() { + bind(ChefAsyncClient.class).to(TransientChefAsyncClient.class).asEagerSingleton(); + } + + @Provides + @Singleton + ChefClient provideClient(TransientChefClient in) { + return in; + } + +} \ No newline at end of file diff --git a/chef/core/src/test/clojure/org/jclouds/chef_test.clj b/chef/core/src/test/clojure/org/jclouds/chef_test.clj new file mode 100644 index 0000000000..8b74be37f8 --- /dev/null +++ b/chef/core/src/test/clojure/org/jclouds/chef_test.clj @@ -0,0 +1,69 @@ +; +; +; Copyright (C) 2010 Cloud Conscious, LLC. +; +; ==================================================================== +; Licensed 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. +; ==================================================================== +; + +(ns org.jclouds.chef-test + (:use [org.jclouds.chef] :reload-all) + (:use [clojure.test])) + +(defn clean-stub-fixture + "This should allow basic tests to easily be run with another service." + [service account key & options] + (fn [f] + (with-chef-service [(apply chef-service service account key options)] +(doseq [databag (databags)] + (delete-databag databag)) +(f)))) + +(use-fixtures :each (clean-stub-fixture "transientchef" "" "")) + +(deftest chef-service?-test + (is (chef-service? *chef*))) + +(deftest as-chef-service-test + (is (chef-service? (chef-service "transientchef" "" ""))) + (is (chef-service? (as-chef-service *chef*))) + (is (chef-service? (as-chef-service (chef-context *chef*))))) + +(deftest create-existing-databag-test + (is (not (databag-exists? ""))) + (create-databag "fred") + (is (databag-exists? "fred"))) + +(deftest create-databag-test + (create-databag "fred") + (is (databag-exists? "fred"))) + +(deftest databags-test + (is (empty? (databags))) + (create-databag "fred") + (is (= 1 (count (databags))))) + +(deftest databag-items-test + (create-databag "databag") + (is (empty? (databag-items "databag"))) + (is (create-databag-item "databag" {:id "databag-item1" :value "databag-value1"})) + (is (create-databag-item "databag" {:id "databag-item2" :value "databag-value2"})) + (is (= 2 (count (databag-items "databag"))))) + +(deftest databag-item-test + (create-databag "databag") + (is (create-databag-item "databag" {:id "databag-item1" :value "databag-value1"})) + (is (create-databag-item "databag" {:id "databag-item2" :value "databag-value2"})) + (is (= {:id "databag-item2" :value "databag-value2"} (databag-item "databag" "databag-item2")))) + diff --git a/chef/core/src/test/java/org/jclouds/chef/BaseChefClientLiveTest.java b/chef/core/src/test/java/org/jclouds/chef/BaseChefClientLiveTest.java index d2970fb03e..e97da0ec2f 100644 --- a/chef/core/src/test/java/org/jclouds/chef/BaseChefClientLiveTest.java +++ b/chef/core/src/test/java/org/jclouds/chef/BaseChefClientLiveTest.java @@ -84,7 +84,7 @@ public abstract class BaseChefClientLiveTest { private Node node; private Role role; protected Json json; - private DatabagItem databagItem; + protected DatabagItem databagItem; public static final String PREFIX = System.getProperty("user.name") + "-jcloudstest"; public BaseChefClientLiveTest() { diff --git a/chef/core/src/test/java/org/jclouds/chef/test/TransientChefClientIntegrationTest.java b/chef/core/src/test/java/org/jclouds/chef/test/TransientChefClientIntegrationTest.java new file mode 100644 index 0000000000..897e78dd17 --- /dev/null +++ b/chef/core/src/test/java/org/jclouds/chef/test/TransientChefClientIntegrationTest.java @@ -0,0 +1,327 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.chef.test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + +import java.io.IOException; +import java.util.Properties; + +import org.jclouds.chef.BaseChefClientLiveTest; +import org.jclouds.chef.ChefClient; +import org.jclouds.chef.ChefContext; +import org.jclouds.chef.ChefContextFactory; +import org.jclouds.chef.config.ChefParserModule; +import org.jclouds.chef.domain.DatabagItem; +import org.jclouds.json.Json; +import org.jclouds.json.config.GsonModule; +import org.jclouds.rest.HttpClient; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import com.google.inject.Guice; + +/** + * Tests behavior of {@code TransientChefClient} + * + * @author Adrian Cole + */ +@Test(groups = "integration", testName = "chef.TransientChefClientIntegrationTest") +public class TransientChefClientIntegrationTest extends BaseChefClientLiveTest { + public void testCreateDatabag1() throws Exception { + getAdminConnection().deleteDatabag(PREFIX); + getAdminConnection().createDatabag(PREFIX); + } + + @Test(dependsOnMethods = "testCreateDatabag1") + public void testDatabagExists1() throws Exception { + assertNotNull(getClientConnection().databagExists(PREFIX)); + } + + @Test(dependsOnMethods = { "testCreateDatabag1"}) + public void testCreateDatabagItem1() throws Exception { + Properties config = new Properties(); + config.setProperty("foo", "bar"); + getAdminConnection().deleteDatabagItem(PREFIX, PREFIX); + databagItem = getAdminConnection().createDatabagItem(PREFIX, new DatabagItem("config", json.toJson(config))); + assertNotNull(databagItem); + assertEquals(databagItem.getId(), "config"); + assertEquals(config, json.fromJson(databagItem.toString(), Properties.class)); + } + + @Test(dependsOnMethods = "testCreateDatabagItem1") + public void testDatabagItemExists1() throws Exception { + assertNotNull(getClientConnection().databagItemExists(PREFIX, PREFIX)); + } + + @Test(dependsOnMethods = "testDatabagItemExists1") + public void testUpdateDatabagItem1() throws Exception { + for (String databagItemId : getClientConnection().listDatabagItems(PREFIX)) { + DatabagItem databagItem = getAdminConnection().getDatabagItem(PREFIX, databagItemId); + getAdminConnection().updateDatabagItem(PREFIX, databagItem); + } + } + @Override + @Test(enabled = false) + public void testClientExists() throws Exception { + super.testClientExists(); + } + + @Override + public void testCreateClient() throws Exception { + } + + @Override + public void testCreateDatabag() throws Exception { + super.testCreateDatabag(); + } + + @Override + public void testCreateDatabagItem() throws Exception { + super.testCreateDatabagItem(); + } + + @Override + public void testDatabagExists() throws Exception { + super.testDatabagExists(); + } + + @Override + public void testDatabagItemExists() throws Exception { + super.testDatabagItemExists(); + } + + @Override + public void testListDatabagItems() throws Exception { + super.testListDatabagItems(); + } + + @Override + public void testListDatabags() throws Exception { + super.testListDatabags(); + } + + @Override + @Test(enabled = false) + public void testCreateCookbook() throws Exception { + super.testCreateCookbook(); + } + + @Override + @Test(enabled = false) + public void testCreateNewCookbook() throws Exception { + super.testCreateNewCookbook(); + } + + @Override + @Test(enabled = false) + public void testCreateNode() throws Exception { + super.testCreateNode(); + } + + @Override + @Test(enabled = false) + public void testCreateRole() throws Exception { + super.testCreateRole(); + } + + @Override + @Test(enabled = false) + public void testGenerateKeyForClient() throws Exception { + super.testGenerateKeyForClient(); + } + + @Override + @Test(enabled = false) + public void testListCookbooks() throws Exception { + super.testListCookbooks(); + } + + @Override + @Test(enabled = false) + public void testListNodes() throws Exception { + super.testListNodes(); + } + + @Override + @Test(enabled = false) + public void testListRoles() throws Exception { + super.testListRoles(); + } + + @Override + @Test(enabled = false) + public void testListSearchIndexes() throws Exception { + super.testListSearchIndexes(); + } + + @Override + @Test(enabled = false) + public void testNodeExists() throws Exception { + super.testNodeExists(); + } + + @Override + @Test(enabled = false) + public void testRoleExists() throws Exception { + super.testRoleExists(); + } + + @Override + @Test(enabled = false) + public void testSearchClients() throws Exception { + super.testSearchClients(); + } + + @Override + @Test(enabled = false) + public void testSearchDatabag() throws Exception { + super.testSearchDatabag(); + } + + @Override + @Test(enabled = false) + public void testSearchDatabagNotFound() throws Exception { + super.testSearchDatabagNotFound(); + } + + @Override + @Test(enabled = false) + public void testSearchNodes() throws Exception { + super.testSearchNodes(); + } + + @Override + @Test(enabled = false) + public void testSearchRoles() throws Exception { + super.testSearchRoles(); + } + + @Override + @Test(enabled = false) + public void testUpdateCookbook() throws Exception { + super.testUpdateCookbook(); + } + + @Override + @Test(enabled = false) + public void testUpdateDatabagItem() throws Exception { + super.testUpdateDatabagItem(); + } + + @Override + @Test(enabled = false) + public void testUpdateNode() throws Exception { + super.testUpdateNode(); + } + + @Override + @Test(enabled = false) + public void testUpdateRole() throws Exception { + super.testUpdateRole(); + } + + @Override + @Test(enabled = false) + public void testValidatorCannotCreateClient() throws Exception { + super.testValidatorCannotCreateClient(); + } + + @Override + @Test(enabled = false) + public void testValidatorCannotDeleteClient() throws Exception { + super.testValidatorCannotDeleteClient(); + } + + @Override + @Test(enabled = false) + public void testValidatorCannotListClients() throws Exception { + super.testValidatorCannotListClients(); + } + + private ChefContext validatorConnection; + private ChefContext clientConnection; + private ChefContext adminConnection; + + @Override + @BeforeClass(groups = { "integration" }) + public void setupClient() throws IOException { + // TODO make this nicer + validatorConnection = adminConnection = clientConnection = createConnection("user", "userkey"); + json = Guice.createInjector(new GsonModule(), new ChefParserModule()).getInstance(Json.class); + } + + @Override + @AfterClass(groups = { "live" }) + public void teardownClient() throws IOException { + // if (getValidatorConnection().clientExists(PREFIX)) + // getValidatorConnection().deleteClient(PREFIX); + // if (getAdminConnection().nodeExists(PREFIX)) + // getAdminConnection().deleteNode(PREFIX); + // if (getAdminConnection().roleExists(PREFIX)) + // getAdminConnection().deleteRole(PREFIX); + if (getAdminConnection().databagExists(PREFIX)) + getAdminConnection().deleteDatabag(PREFIX); + closeContexts(); + } + + private ChefContext createConnection(String identity, String key) throws IOException { + return new ChefContextFactory().createContext("transientchef", identity, key); + } + + @Override + protected HttpClient getHttp() { + return adminConnection.utils().http(); + } + + @Override + protected ChefClient getAdminConnection() { + return adminConnection.getApi(); + } + + @Override + protected ChefClient getValidatorConnection() { + return validatorConnection.getApi(); + } + + @Override + protected ChefClient getClientConnection() { + return clientConnection.getApi(); + } + + @Override + protected void recreateClientConnection() throws IOException { + if (clientConnection != null) + clientConnection.close(); + clientConnection = createConnection(PREFIX, clientKey); + } + + @Override + protected void closeContexts() { + if (clientConnection != null) + clientConnection.close(); + if (validatorConnection != null) + validatorConnection.close(); + if (adminConnection != null) + adminConnection.close(); + } +} diff --git a/core/src/main/resources/rest.properties b/core/src/main/resources/rest.properties index c50d2b83dd..95cfa60f79 100644 --- a/core/src/main/resources/rest.properties +++ b/core/src/main/resources/rest.properties @@ -62,6 +62,9 @@ hostingdotcom.propertiesbuilder=org.jclouds.vcloud.hostingdotcom.HostingDotComVC chef.contextbuilder=org.jclouds.chef.ChefContextBuilder chef.propertiesbuilder=org.jclouds.chef.ChefPropertiesBuilder +transientchef.contextbuilder=org.jclouds.chef.test.TransientChefContextBuilder +transientchef.propertiesbuilder=org.jclouds.chef.ChefPropertiesBuilder + opscodeplatform.contextbuilder=org.jclouds.opscodeplatform.OpscodePlatformContextBuilder opscodeplatform.propertiesbuilder=org.jclouds.opscodeplatform.OpscodePlatformPropertiesBuilder