Updated Branches:
  refs/heads/master 981db3465 -> 8b94ee589

JCLOUDS-218. List templates in all known projects for CloudStack.


Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/8b94ee58
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/8b94ee58
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/8b94ee58

Branch: refs/heads/master
Commit: 8b94ee589bb9a5a6df3823569a55775f66cef502
Parents: 981db34
Author: Andrew Bayer <[email protected]>
Authored: Mon Nov 25 16:43:46 2013 -0800
Committer: Andrew Bayer <[email protected]>
Committed: Tue Nov 26 10:00:09 2013 -0800

----------------------------------------------------------------------
 .../org/jclouds/cloudstack/CloudStackApi.java   |   7 +
 .../CloudStackComputeServiceContextModule.java  |  11 +
 .../CloudStackComputeServiceAdapter.java        |  25 +-
 .../org/jclouds/cloudstack/domain/Project.java  | 243 +++++++++++++++++++
 .../jclouds/cloudstack/features/ProjectApi.java |  77 ++++++
 .../cloudstack/options/ListProjectsOptions.java | 171 +++++++++++++
 .../suppliers/ProjectsForCurrentUser.java       |  64 +++++
 ...oudStackComputeServiceAdapterExpectTest.java | 149 ++++++------
 .../features/ProjectApiExpectTest.java          |  79 ++++++
 .../cloudstack/features/ProjectApiLiveTest.java |  48 ++++
 .../cloudstack/features/ProjectApiTest.java     |  98 ++++++++
 ...oudStackComputeServiceContextExpectTest.java |  17 +-
 .../test/resources/listprojectsresponse.json    |   1 +
 13 files changed, 911 insertions(+), 79 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/8b94ee58/apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackApi.java
----------------------------------------------------------------------
diff --git 
a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackApi.java 
b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackApi.java
index 9959421..b9a2ca8 100644
--- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackApi.java
+++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackApi.java
@@ -32,6 +32,7 @@ import org.jclouds.cloudstack.features.LoadBalancerApi;
 import org.jclouds.cloudstack.features.NATApi;
 import org.jclouds.cloudstack.features.NetworkApi;
 import org.jclouds.cloudstack.features.OfferingApi;
+import org.jclouds.cloudstack.features.ProjectApi;
 import org.jclouds.cloudstack.features.SSHKeyPairApi;
 import org.jclouds.cloudstack.features.SecurityGroupApi;
 import org.jclouds.cloudstack.features.SessionApi;
@@ -189,4 +190,10 @@ public interface CloudStackApi extends Closeable {
     */
    @Delegate
    SessionApi getSessionApi();
+
+   /**
+    * Provides synchronous access to Projects
+    */
+   @Delegate
+   ProjectApi getProjectApi();
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/8b94ee58/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/config/CloudStackComputeServiceContextModule.java
----------------------------------------------------------------------
diff --git 
a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/config/CloudStackComputeServiceContextModule.java
 
b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/config/CloudStackComputeServiceContextModule.java
index 9ed08de..b06b396 100644
--- 
a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/config/CloudStackComputeServiceContextModule.java
+++ 
b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/config/CloudStackComputeServiceContextModule.java
@@ -53,6 +53,7 @@ import org.jclouds.cloudstack.domain.IngressRule;
 import org.jclouds.cloudstack.domain.Network;
 import org.jclouds.cloudstack.domain.NetworkType;
 import org.jclouds.cloudstack.domain.OSType;
+import org.jclouds.cloudstack.domain.Project;
 import org.jclouds.cloudstack.domain.SecurityGroup;
 import org.jclouds.cloudstack.domain.ServiceOffering;
 import org.jclouds.cloudstack.domain.SshKeyPair;
@@ -71,6 +72,7 @@ import org.jclouds.cloudstack.functions.ZoneIdToZone;
 import org.jclouds.cloudstack.predicates.JobComplete;
 import org.jclouds.cloudstack.suppliers.GetCurrentUser;
 import org.jclouds.cloudstack.suppliers.NetworksForCurrentUser;
+import org.jclouds.cloudstack.suppliers.ProjectsForCurrentUser;
 import org.jclouds.cloudstack.suppliers.ZoneIdToZoneSupplier;
 import org.jclouds.collect.Memoized;
 import org.jclouds.compute.ComputeServiceAdapter;
@@ -223,6 +225,15 @@ public class CloudStackComputeServiceContextModule extends
    @Provides
    @Singleton
    @Memoized
+   public Supplier<Map<String, Project>> 
listProjects(AtomicReference<AuthorizationException> authException, 
@Named(PROPERTY_SESSION_INTERVAL) long seconds,
+                                                      final 
ProjectsForCurrentUser projectsForCurrentUser) {
+      return 
MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier.create(authException,
 projectsForCurrentUser,
+              seconds, TimeUnit.SECONDS);
+   }
+
+   @Provides
+   @Singleton
+   @Memoized
    public Supplier<User> 
getCurrentUser(AtomicReference<AuthorizationException> authException, 
@Named(PROPERTY_SESSION_INTERVAL) long seconds,
          final GetCurrentUser getCurrentUser) {
       return 
MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier.create(authException,
 getCurrentUser,

http://git-wip-us.apache.org/repos/asf/jclouds/blob/8b94ee58/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/strategy/CloudStackComputeServiceAdapter.java
----------------------------------------------------------------------
diff --git 
a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/strategy/CloudStackComputeServiceAdapter.java
 
b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/strategy/CloudStackComputeServiceAdapter.java
index a04b9fe..6961ab7 100644
--- 
a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/strategy/CloudStackComputeServiceAdapter.java
+++ 
b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/strategy/CloudStackComputeServiceAdapter.java
@@ -19,9 +19,11 @@ package org.jclouds.cloudstack.compute.strategy;
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.Iterables.concat;
 import static com.google.common.collect.Iterables.contains;
 import static com.google.common.collect.Iterables.filter;
 import static com.google.common.collect.Iterables.get;
+import static com.google.common.collect.Iterables.transform;
 import static 
org.jclouds.cloudstack.options.DeployVirtualMachineOptions.Builder.displayName;
 import static org.jclouds.cloudstack.options.ListTemplatesOptions.Builder.id;
 import static org.jclouds.cloudstack.predicates.TemplatePredicates.isReady;
@@ -31,7 +33,6 @@ import static org.jclouds.ssh.SshKeys.fingerprintPrivateKey;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.ExecutionException;
 
 import javax.annotation.Resource;
 import javax.inject.Inject;
@@ -46,6 +47,7 @@ import org.jclouds.cloudstack.domain.FirewallRule;
 import org.jclouds.cloudstack.domain.IPForwardingRule;
 import org.jclouds.cloudstack.domain.Network;
 import org.jclouds.cloudstack.domain.NetworkType;
+import org.jclouds.cloudstack.domain.Project;
 import org.jclouds.cloudstack.domain.PublicIPAddress;
 import org.jclouds.cloudstack.domain.SecurityGroup;
 import org.jclouds.cloudstack.domain.ServiceOffering;
@@ -55,12 +57,14 @@ import org.jclouds.cloudstack.domain.VirtualMachine;
 import org.jclouds.cloudstack.domain.Zone;
 import org.jclouds.cloudstack.domain.ZoneAndName;
 import org.jclouds.cloudstack.domain.ZoneSecurityGroupNamePortsCidrs;
+import org.jclouds.cloudstack.features.TemplateApi;
 import org.jclouds.cloudstack.functions.CreateFirewallRulesForIP;
 import org.jclouds.cloudstack.functions.CreatePortForwardingRulesForIP;
 import org.jclouds.cloudstack.functions.StaticNATVirtualMachineInNetwork;
 import 
org.jclouds.cloudstack.functions.StaticNATVirtualMachineInNetwork.Factory;
 import org.jclouds.cloudstack.options.DeployVirtualMachineOptions;
 import org.jclouds.cloudstack.options.ListFirewallRulesOptions;
+import org.jclouds.cloudstack.options.ListTemplatesOptions;
 import org.jclouds.cloudstack.strategy.BlockUntilJobCompletesAndReturnResult;
 import org.jclouds.collect.Memoized;
 import org.jclouds.compute.ComputeServiceAdapter;
@@ -70,14 +74,13 @@ import org.jclouds.domain.Credentials;
 import org.jclouds.domain.LoginCredentials;
 import org.jclouds.logging.Logger;
 
+import com.google.common.base.Function;
 import com.google.common.base.Predicate;
 import com.google.common.base.Supplier;
-import com.google.common.base.Throwables;
 import com.google.common.cache.LoadingCache;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Sets;
 import com.google.common.collect.ImmutableSet.Builder;
+import com.google.common.collect.Sets;
 import com.google.common.primitives.Ints;
 
 /**
@@ -95,6 +98,7 @@ public class CloudStackComputeServiceAdapter implements
    private final CloudStackApi client;
    private final Predicate<String> jobComplete;
    private final Supplier<Map<String, Network>> networkSupplier;
+   private final Supplier<Map<String, Project>> projectSupplier;
    private final BlockUntilJobCompletesAndReturnResult 
blockUntilJobCompletesAndReturnResult;
    private final Factory staticNATVMInNetwork;
    private final CreatePortForwardingRulesForIP setupPortForwardingRulesForIP;
@@ -110,6 +114,7 @@ public class CloudStackComputeServiceAdapter implements
    @Inject
    public CloudStackComputeServiceAdapter(CloudStackApi client, 
Predicate<String> jobComplete,
                                           @Memoized Supplier<Map<String, 
Network>> networkSupplier,
+                                          @Memoized Supplier<Map<String, 
Project>> projectSupplier,
                                           
BlockUntilJobCompletesAndReturnResult blockUntilJobCompletesAndReturnResult,
                                           
StaticNATVirtualMachineInNetwork.Factory staticNATVMInNetwork,
                                           CreatePortForwardingRulesForIP 
setupPortForwardingRulesForIP,
@@ -124,6 +129,7 @@ public class CloudStackComputeServiceAdapter implements
       this.client = checkNotNull(client, "client");
       this.jobComplete = checkNotNull(jobComplete, "jobComplete");
       this.networkSupplier = checkNotNull(networkSupplier, "networkSupplier");
+      this.projectSupplier = checkNotNull(projectSupplier, "projectSupplier");
       this.blockUntilJobCompletesAndReturnResult = 
checkNotNull(blockUntilJobCompletesAndReturnResult,
          "blockUntilJobCompletesAndReturnResult");
       this.staticNATVMInNetwork = checkNotNull(staticNATVMInNetwork, 
"staticNATVMInNetwork");
@@ -270,9 +276,14 @@ public class CloudStackComputeServiceAdapter implements
 
    @Override
    public Iterable<Template> listImages() {
-      // TODO: we may need to filter these further
-      // we may also want to see if we can work with ssh keys
-      return filter(client.getTemplateApi().listTemplates(), isReady());
+      TemplateApi templateApi = client.getTemplateApi();
+      ImmutableSet.Builder<Template> templates = ImmutableSet.builder();
+      templates.addAll(templateApi.listTemplates());
+      for (String project : projectSupplier.get().keySet()) {
+         
templates.addAll(templateApi.listTemplates(ListTemplatesOptions.Builder.projectId(project)));
+      }
+
+      return filter(templates.build(), isReady());
    }
 
    @Override

http://git-wip-us.apache.org/repos/asf/jclouds/blob/8b94ee58/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/Project.java
----------------------------------------------------------------------
diff --git 
a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/Project.java 
b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/Project.java
new file mode 100644
index 0000000..a58a6f0
--- /dev/null
+++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/Project.java
@@ -0,0 +1,243 @@
+/*
+ * 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.cloudstack.domain;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.beans.ConstructorProperties;
+
+import org.jclouds.javax.annotation.Nullable;
+
+import com.google.common.base.CaseFormat;
+import com.google.common.base.Objects;
+import com.google.common.base.Objects.ToStringHelper;
+
+/**
+ * Representation of the API project response
+ *
+ * @author Andrew Bayer
+ */
+public class Project implements Comparable<Project> {
+
+   public static enum State {
+      ACTIVE, DISABLED, SUSPENDED, UNRECOGNIZED;
+
+      @Override
+      public String toString() {
+         return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_HYPHEN, 
name());
+      }
+
+      public static State fromValue(String state) {
+         try {
+            return 
valueOf(CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_UNDERSCORE, 
checkNotNull(state, "state")));
+         } catch (IllegalArgumentException e) {
+            return UNRECOGNIZED;
+         }
+      }
+
+   }
+
+   public static Builder<?> builder() {
+      return new ConcreteBuilder();
+   }
+
+   public Builder<?> toBuilder() {
+      return new ConcreteBuilder().fromDomain(this);
+   }
+
+   public abstract static class Builder<T extends Builder<T>> {
+      protected abstract T self();
+
+      protected String id;
+      protected String account;
+      protected String displayText;
+      protected String domain;
+      protected String domainId;
+      protected String name;
+      protected State state;
+
+      /**
+       * @see org.jclouds.cloudstack.domain.Project#getId()
+       */
+      public T id(String id) {
+         this.id = id;
+         return self();
+      }
+
+      /**
+       * @see org.jclouds.cloudstack.domain.Project#getName()
+       */
+      public T name(String name) {
+         this.name = name;
+         return self();
+      }
+
+      /**
+       * @see org.jclouds.cloudstack.domain.Project#getDomainId()
+       */
+      public T domainId(String domainId) {
+         this.domainId = domainId;
+         return self();
+      }
+
+      /**
+       * @see org.jclouds.cloudstack.domain.Project#getDomain()
+       */
+      public T domain(String domain) {
+         this.domain = domain;
+         return self();
+      }
+
+      /**
+       * @see org.jclouds.cloudstack.domain.Project#getAccount()
+       */
+      public T account(String account) {
+         this.account = account;
+         return self();
+      }
+
+      /**
+       * @see org.jclouds.cloudstack.domain.Project#getDisplayText()
+       */
+      public T displayText(String displayText) {
+         this.displayText = displayText;
+         return self();
+      }
+
+      /**
+       * @see org.jclouds.cloudstack.domain.Project#getState()
+       */
+      public T state(State state) {
+         this.state = state;
+         return self();
+      }
+
+
+      public Project build() {
+         return new Project(id, account, displayText, domain, domainId, name, 
state);
+      }
+
+      public T fromDomain(Project in) {
+         return this
+                 .id(in.getId())
+                 .account(in.getAccount())
+                 .displayText(in.getDisplayText())
+                 .domain(in.getDomain())
+                 .domainId(in.getDomainId())
+                 .name(in.getName())
+                 .state(in.getState());
+      }
+   }
+
+   private static class ConcreteBuilder extends Builder<ConcreteBuilder> {
+      @Override
+      protected ConcreteBuilder self() {
+         return this;
+      }
+   }
+
+   private final String id;
+   private final String account;
+   private final String displayText;
+   private final String domain;
+   private final String domainId;
+   private final String name;
+   private final State state;
+
+   @ConstructorProperties({
+         "id", "account", "displaytext", "domain", "domainid", "name", "state"
+   })
+   protected Project(String id, String account, String displayText, String 
domain, String domainId,
+                     String name, State state) {
+      this.id = checkNotNull(id, "id");
+      this.account = account;
+      this.displayText = displayText;
+      this.domain = domain;
+      this.domainId = domainId;
+      this.name = name;
+      this.state = checkNotNull(state, "state");
+   }
+
+   public String getId() {
+      return this.id;
+   }
+
+   @Nullable
+   public String getAccount() {
+      return this.account;
+   }
+
+   @Nullable
+   public String getDisplayText() {
+      return this.displayText;
+   }
+
+   @Nullable
+   public String getDomain() {
+      return this.domain;
+   }
+
+   @Nullable
+   public String getDomainId() {
+      return this.domainId;
+   }
+
+   @Nullable
+   public String getName() {
+      return this.name;
+   }
+
+   public State getState() {
+      return this.state;
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(id, account, displayText, domain, domainId, 
name, state);
+   }
+
+   @Override
+   public boolean equals(Object obj) {
+      if (this == obj) return true;
+      if (obj == null || getClass() != obj.getClass()) return false;
+      Project that = Project.class.cast(obj);
+      return Objects.equal(this.id, that.id)
+            && Objects.equal(this.account, that.account)
+            && Objects.equal(this.displayText, that.displayText)
+            && Objects.equal(this.domain, that.domain)
+            && Objects.equal(this.domainId, that.domainId)
+            && Objects.equal(this.name, that.name)
+            && Objects.equal(this.state, that.state);
+   }
+
+   protected ToStringHelper string() {
+      return Objects.toStringHelper(this).omitNullValues()
+            .add("id", id).add("account", account).add("displayText", 
displayText)
+              .add("domain", domain).add("domainId", domainId).add("name", 
name).add("state", state);
+   }
+
+   @Override
+   public String toString() {
+      return string().toString();
+   }
+
+   @Override
+   public int compareTo(Project other) {
+      return id.compareTo(other.getId());
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/8b94ee58/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/ProjectApi.java
----------------------------------------------------------------------
diff --git 
a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/ProjectApi.java 
b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/ProjectApi.java
new file mode 100644
index 0000000..dbe7fa9
--- /dev/null
+++ 
b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/ProjectApi.java
@@ -0,0 +1,77 @@
+/*
+ * 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.cloudstack.features;
+
+import java.util.Set;
+
+import javax.inject.Named;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.Fallbacks.EmptySetOnNotFoundOr404;
+import org.jclouds.Fallbacks.NullOnNotFoundOr404;
+import org.jclouds.cloudstack.domain.Project;
+import org.jclouds.cloudstack.filters.AuthenticationFilter;
+import org.jclouds.cloudstack.options.ListProjectsOptions;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.OnlyElement;
+import org.jclouds.rest.annotations.QueryParams;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.SelectJson;
+
+/**
+ * Provides synchronous access to CloudStack project features.
+ *
+ * @author Andrew Bayer
+ * @see <a
+ *      
href="http://download.cloud.com/releases/3.0.6/api_3.0.6/TOC_Root_Admin.html";
+ *      />
+ */
+@RequestFilters(AuthenticationFilter.class)
+@QueryParams(keys = "response", values = "json")
+public interface ProjectApi {
+   /**
+    * Lists the projects this account has access to.
+    *
+    * @param options if present, how to constrain the list
+    */
+   @Named("listProjects")
+   @GET
+   @QueryParams(keys = { "command", "listAll" }, values = { "listProjects", 
"true" })
+   @SelectJson("project")
+   @Consumes(MediaType.APPLICATION_JSON)
+   @Fallback(EmptySetOnNotFoundOr404.class)
+   Set<Project> listProjects(ListProjectsOptions... options);
+
+   /**
+    * gets a specific Project by id
+    *
+    * @param id
+    *           Project to get
+    * @return Project or null if not found    */
+   @Named("listProjects")
+   @GET
+   @QueryParams(keys = { "command", "listAll" }, values = { "listProjects", 
"true" })
+   @SelectJson("project")
+   @OnlyElement
+   @Consumes(MediaType.APPLICATION_JSON)
+   @Fallback(NullOnNotFoundOr404.class)
+   Project getProject(@QueryParam("id") String id);
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/8b94ee58/apis/cloudstack/src/main/java/org/jclouds/cloudstack/options/ListProjectsOptions.java
----------------------------------------------------------------------
diff --git 
a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/options/ListProjectsOptions.java
 
b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/options/ListProjectsOptions.java
new file mode 100644
index 0000000..db67ed5
--- /dev/null
+++ 
b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/options/ListProjectsOptions.java
@@ -0,0 +1,171 @@
+/*
+ * 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.cloudstack.options;
+
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Options used to control what account information is returned
+ * 
+ * @see <a
+ *      
href="http://download.cloud.com/releases/3.0.6/api_3.0.6/root_admin/listProjects.html";
+ *      />
+ * @author Andrew Bayer
+ */
+public class ListProjectsOptions extends AccountInDomainOptions {
+
+   public static final ListProjectsOptions NONE = new ListProjectsOptions();
+
+   /**
+    * @param id
+    *           list projects by project ID
+    */
+   public ListProjectsOptions id(String id) {
+      this.queryParameters.replaceValues("id", ImmutableSet.of(id));
+      return this;
+   }
+
+   /**
+    * @param name
+    *           list project by project name
+    */
+   public ListProjectsOptions name(String name) {
+      this.queryParameters.replaceValues("name", ImmutableSet.of(name));
+      return this;
+   }
+
+   /**
+    * @param state
+    *           list projects by state. Valid states are enabled, disabled, and
+    *           locked.
+    */
+   public ListProjectsOptions state(String state) {
+      this.queryParameters.replaceValues("state", ImmutableSet.of(state));
+      return this;
+   }
+
+   /**
+    * @param displayText
+    *           list projects by displayText.
+    */
+   public ListProjectsOptions displayText(String displayText) {
+      this.queryParameters.replaceValues("displaytext", 
ImmutableSet.of(displayText));
+      return this;
+   }
+
+   /**
+    * @param keyword
+    *           list projects by keyword.
+    */
+   public ListProjectsOptions keyword(String keyword) {
+      this.queryParameters.replaceValues("keyword", ImmutableSet.of(keyword));
+      return this;
+   }
+
+   /**
+    * @param recursive
+    *           defaults to false, but if true, lists all projects from the
+    *           parent specified by the domain id till leaves.
+    */
+   public ListProjectsOptions recursive(boolean recursive) {
+      this.queryParameters.replaceValues("isrecursive", 
ImmutableSet.of(recursive + ""));
+      return this;
+   }
+
+   public static class Builder {
+
+      /**
+       * @see 
org.jclouds.cloudstack.options.ListProjectsOptions#accountInDomain(String, 
String)
+       */
+      public static ListProjectsOptions accountInDomain(String project, String 
domain) {
+         ListProjectsOptions options = new ListProjectsOptions();
+         return options.accountInDomain(project, domain);
+      }
+
+      /**
+       * @see org.jclouds.cloudstack.options.ListProjectsOptions#domainId
+       */
+      public static ListProjectsOptions domainId(String domainId) {
+         ListProjectsOptions options = new ListProjectsOptions();
+         return options.domainId(domainId);
+      }
+
+      /**
+       * @see org.jclouds.cloudstack.options.ListProjectsOptions#id
+       */
+      public static ListProjectsOptions id(String id) {
+         ListProjectsOptions options = new ListProjectsOptions();
+         return options.id(id);
+      }
+
+      /**
+       * @see org.jclouds.cloudstack.options.ListProjectsOptions#name
+       */
+      public static ListProjectsOptions name(String name) {
+         ListProjectsOptions options = new ListProjectsOptions();
+         return options.name(name);
+      }
+
+      /**
+       * @see org.jclouds.cloudstack.options.ListProjectsOptions#state
+       */
+      public static ListProjectsOptions state(String state) {
+         ListProjectsOptions options = new ListProjectsOptions();
+         return options.state(state);
+      }
+
+      /**
+       * @see 
org.jclouds.cloudstack.options.ListProjectsOptions#displayText(String)
+       */
+      public static ListProjectsOptions displayText(String displayText) {
+         ListProjectsOptions options = new ListProjectsOptions();
+         return options.displayText(displayText);
+      }
+
+      /**
+       * @see 
org.jclouds.cloudstack.options.ListProjectsOptions#keyword(String)
+       */
+      public static ListProjectsOptions keyword(String keyword) {
+         ListProjectsOptions options = new ListProjectsOptions();
+         return options.keyword(keyword);
+      }
+
+      /**
+       * @see org.jclouds.cloudstack.options.ListProjectsOptions#recursive
+       */
+      public static ListProjectsOptions recursive(boolean recursive) {
+         ListProjectsOptions options = new ListProjectsOptions();
+         return options.recursive(recursive);
+      }
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public ListProjectsOptions accountInDomain(String account, String domain) {
+      return ListProjectsOptions.class.cast(super.accountInDomain(account, 
domain));
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public ListProjectsOptions domainId(String domainId) {
+      return ListProjectsOptions.class.cast(super.domainId(domainId));
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/8b94ee58/apis/cloudstack/src/main/java/org/jclouds/cloudstack/suppliers/ProjectsForCurrentUser.java
----------------------------------------------------------------------
diff --git 
a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/suppliers/ProjectsForCurrentUser.java
 
b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/suppliers/ProjectsForCurrentUser.java
new file mode 100644
index 0000000..9422276
--- /dev/null
+++ 
b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/suppliers/ProjectsForCurrentUser.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.cloudstack.suppliers;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static 
org.jclouds.cloudstack.options.ListProjectsOptions.Builder.accountInDomain;
+
+import java.util.Map;
+
+import javax.inject.Inject;
+
+import org.jclouds.cloudstack.CloudStackApi;
+import org.jclouds.cloudstack.domain.Project;
+import org.jclouds.cloudstack.domain.User;
+import org.jclouds.cloudstack.features.ProjectApi;
+import org.jclouds.collect.Memoized;
+
+import com.google.common.base.Function;
+import com.google.common.base.Supplier;
+import com.google.common.collect.Maps;
+
+/**
+ * 
+ * @author Andrew Bayer
+ */
+public class ProjectsForCurrentUser implements Supplier<Map<String, Project>> {
+   private final CloudStackApi api;
+   private final Supplier<User> currentUserSupplier;
+
+   @Inject
+   public ProjectsForCurrentUser(CloudStackApi api, @Memoized Supplier<User> 
currentUserSupplier) {
+      this.api = checkNotNull(api, "api");
+      this.currentUserSupplier = checkNotNull(currentUserSupplier, 
"currentUserSupplier");
+   }
+
+   @Override
+   public Map<String, Project> get() {
+      User currentUser = currentUserSupplier.get();
+      ProjectApi projectApi = api.getProjectApi();
+      return Maps.uniqueIndex(
+            projectApi.listProjects(accountInDomain(currentUser.getAccount(), 
currentUser.getDomainId())),
+            new Function<Project, String>() {
+
+               @Override
+               public String apply(Project arg0) {
+                  return arg0.getId();
+               }
+            });
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/8b94ee58/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackComputeServiceAdapterExpectTest.java
----------------------------------------------------------------------
diff --git 
a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackComputeServiceAdapterExpectTest.java
 
b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackComputeServiceAdapterExpectTest.java
index be888cc..112c7bd 100644
--- 
a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackComputeServiceAdapterExpectTest.java
+++ 
b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackComputeServiceAdapterExpectTest.java
@@ -110,17 +110,18 @@ public class CloudStackComputeServiceAdapterExpectTest 
extends BaseCloudStackCom
               .build();
   
       Map<HttpRequest, HttpResponse> requestResponseMap = 
ImmutableMap.<HttpRequest, HttpResponse> builder()
-            .put(listTemplates, listTemplatesResponse)
-            .put(listOsTypes, listOsTypesResponse)
-            .put(listOsCategories, listOsCategoriesResponse)
-            .put(listZones, listZonesResponse)
-            .put(listServiceOfferings, listServiceOfferingsResponse)
-            .put(listAccounts, listAccountsResponse)
-            .put(listNetworks, listNetworksResponse)
-            .put(getZone, getZoneResponse)
-            .put(deployVM, deployVMResponse)
-            .put(queryAsyncJobResult, queryAsyncJobResultResponse)
-            .build();
+              .put(listProjects, listProjectsResponse)
+              .put(listTemplates, listTemplatesResponse)
+              .put(listOsTypes, listOsTypesResponse)
+              .put(listOsCategories, listOsCategoriesResponse)
+              .put(listZones, listZonesResponse)
+              .put(listServiceOfferings, listServiceOfferingsResponse)
+              .put(listAccounts, listAccountsResponse)
+              .put(listNetworks, listNetworksResponse)
+              .put(getZone, getZoneResponse)
+              .put(deployVM, deployVMResponse)
+              .put(queryAsyncJobResult, queryAsyncJobResultResponse)
+              .build();
 
       Injector forNode = requestsSendResponses(requestResponseMap);
 
@@ -154,17 +155,18 @@ public class CloudStackComputeServiceAdapterExpectTest 
extends BaseCloudStackCom
               .build();
   
       Map<HttpRequest, HttpResponse> requestResponseMap = 
ImmutableMap.<HttpRequest, HttpResponse> builder()
-            .put(listTemplates, listTemplatesResponse)
-            .put(listOsTypes, listOsTypesResponse)
-            .put(listOsCategories, listOsCategoriesResponse)
-            .put(listZones, listZonesResponse)
-            .put(listServiceOfferings, listServiceOfferingsResponse)
-            .put(listAccounts, listAccountsResponse)
-            .put(listNetworks, listNetworksResponse)
-            .put(getZone, getZoneResponse)
-            .put(deployVM, deployVMResponse)
-            .put(queryAsyncJobResult, queryAsyncJobResultResponse)
-            .build();
+              .put(listProjects, listProjectsResponse)
+              .put(listTemplates, listTemplatesResponse)
+              .put(listOsTypes, listOsTypesResponse)
+              .put(listOsCategories, listOsCategoriesResponse)
+              .put(listZones, listZonesResponse)
+              .put(listServiceOfferings, listServiceOfferingsResponse)
+              .put(listAccounts, listAccountsResponse)
+              .put(listNetworks, listNetworksResponse)
+              .put(getZone, getZoneResponse)
+              .put(deployVM, deployVMResponse)
+              .put(queryAsyncJobResult, queryAsyncJobResultResponse)
+              .build();
 
       Injector forKeyPair = requestsSendResponses(requestResponseMap);
 
@@ -201,18 +203,19 @@ public class CloudStackComputeServiceAdapterExpectTest 
extends BaseCloudStackCom
               .build();
   
       Map<HttpRequest, HttpResponse> requestResponseMap = 
ImmutableMap.<HttpRequest, HttpResponse> builder()
-            .put(listTemplates, listTemplatesResponse)
-            .put(listOsTypes, listOsTypesResponse)
-            .put(listOsCategories, listOsCategoriesResponse)
-            .put(listZones, listZonesResponse)
-            .put(listServiceOfferings, listServiceOfferingsResponse)
-            .put(listAccounts, listAccountsResponse)
-            .put(listNetworks, listNetworksResponse)
-            .put(getZone, getZoneResponse)
-            .put(deployVM, deployVMResponse)
-            .put(createSSHKeyPair, createSSHKeyPairResponse)
-            .put(queryAsyncJobResult, queryAsyncJobResultResponse)
-            .build();
+              .put(listProjects, listProjectsResponse)
+              .put(listTemplates, listTemplatesResponse)
+              .put(listOsTypes, listOsTypesResponse)
+              .put(listOsCategories, listOsCategoriesResponse)
+              .put(listZones, listZonesResponse)
+              .put(listServiceOfferings, listServiceOfferingsResponse)
+              .put(listAccounts, listAccountsResponse)
+              .put(listNetworks, listNetworksResponse)
+              .put(getZone, getZoneResponse)
+              .put(deployVM, deployVMResponse)
+              .put(createSSHKeyPair, createSSHKeyPairResponse)
+              .put(queryAsyncJobResult, queryAsyncJobResultResponse)
+              .build();
 
       Injector forKeyPair = requestsSendResponses(requestResponseMap);
 
@@ -247,17 +250,18 @@ public class CloudStackComputeServiceAdapterExpectTest 
extends BaseCloudStackCom
               .build();
   
       Map<HttpRequest, HttpResponse> requestResponseMap = 
ImmutableMap.<HttpRequest, HttpResponse> builder()
-            .put(listTemplates, listTemplatesResponse)
-            .put(listOsTypes, listOsTypesResponse)
-            .put(listOsCategories, listOsCategoriesResponse)
-            .put(listZones, listZonesResponse)
-            .put(listServiceOfferings, listServiceOfferingsResponse)
-            .put(listAccounts, listAccountsResponse)
-            .put(listNetworks, listNetworksWithSecurityGroupsResponse)
-            .put(getZoneWithSecurityGroups, getZoneWithSecurityGroupsResponse)
-            .put(deployVM, deployVMResponse)
-            .put(queryAsyncJobResult, queryAsyncJobResultResponse)
-            .build();
+              .put(listProjects, listProjectsResponse)
+              .put(listTemplates, listTemplatesResponse)
+              .put(listOsTypes, listOsTypesResponse)
+              .put(listOsCategories, listOsCategoriesResponse)
+              .put(listZones, listZonesResponse)
+              .put(listServiceOfferings, listServiceOfferingsResponse)
+              .put(listAccounts, listAccountsResponse)
+              .put(listNetworks, listNetworksWithSecurityGroupsResponse)
+              .put(getZoneWithSecurityGroups, 
getZoneWithSecurityGroupsResponse)
+              .put(deployVM, deployVMResponse)
+              .put(queryAsyncJobResult, queryAsyncJobResultResponse)
+              .build();
 
       Injector forKeyPair = requestsSendResponses(requestResponseMap);
 
@@ -299,6 +303,7 @@ public class CloudStackComputeServiceAdapterExpectTest 
extends BaseCloudStackCom
               .build();
 
       Map<HttpRequest, HttpResponse> requestResponseMap = 
ImmutableMap.<HttpRequest, HttpResponse> builder()
+              .put(listProjects, listProjectsResponse)
               .put(listTemplates, listTemplatesResponse)
               .put(listOsTypes, listOsTypesResponse)
               .put(listOsCategories, listOsCategoriesResponse)
@@ -353,21 +358,22 @@ public class CloudStackComputeServiceAdapterExpectTest 
extends BaseCloudStackCom
               .build();
   
       Map<HttpRequest, HttpResponse> requestResponseMap = 
ImmutableMap.<HttpRequest, HttpResponse> builder()
-            .put(listTemplates, listTemplatesResponse)
-            .put(listOsTypes, listOsTypesResponse)
-            .put(listOsCategories, listOsCategoriesResponse)
-            .put(listZones, listZonesResponse)
-            .put(listServiceOfferings, listServiceOfferingsResponse)
-            .put(listAccounts, listAccountsResponse)
-            .put(listNetworks, listNetworksWithSecurityGroupsResponse)
-            .put(getZoneWithSecurityGroups, getZoneWithSecurityGroupsResponse)
-            .put(deployVM, deployVMResponse)
-            .put(queryAsyncJobResult, queryAsyncJobResultSecurityGroupResponse)
-            .put(queryAsyncJobResultAuthorizeIngress, 
queryAsyncJobResultAuthorizeIngressResponse)
-            .put(getSecurityGroup, getSecurityGroupResponse)
-            .put(createSecurityGroup, createSecurityGroupResponse)
-            .put(authorizeIngress, authorizeIngressResponse)
-            .build();
+              .put(listProjects, listProjectsResponse)
+              .put(listTemplates, listTemplatesResponse)
+              .put(listOsTypes, listOsTypesResponse)
+              .put(listOsCategories, listOsCategoriesResponse)
+              .put(listZones, listZonesResponse)
+              .put(listServiceOfferings, listServiceOfferingsResponse)
+              .put(listAccounts, listAccountsResponse)
+              .put(listNetworks, listNetworksWithSecurityGroupsResponse)
+              .put(getZoneWithSecurityGroups, 
getZoneWithSecurityGroupsResponse)
+              .put(deployVM, deployVMResponse)
+              .put(queryAsyncJobResult, 
queryAsyncJobResultSecurityGroupResponse)
+              .put(queryAsyncJobResultAuthorizeIngress, 
queryAsyncJobResultAuthorizeIngressResponse)
+              .put(getSecurityGroup, getSecurityGroupResponse)
+              .put(createSecurityGroup, createSecurityGroupResponse)
+              .put(authorizeIngress, authorizeIngressResponse)
+              .build();
 
       Injector forKeyPair = requestsSendResponses(requestResponseMap);
 
@@ -411,17 +417,18 @@ public class CloudStackComputeServiceAdapterExpectTest 
extends BaseCloudStackCom
               .build();
 
       Map<HttpRequest, HttpResponse> requestResponseMap = 
ImmutableMap.<HttpRequest, HttpResponse> builder()
-            .put(listTemplates, listTemplatesResponse)
-            .put(listOsTypes, listOsTypesResponse)
-            .put(listOsCategories, listOsCategoriesResponse)
-            .put(listZones, listZonesResponse)
-            .put(listServiceOfferings, listServiceOfferingsResponse)
-            .put(listAccounts, listAccountsResponse)
-            .put(listNetworks, listNetworksResponse)
-            .put(getZone, getZoneResponse)
-            .put(deployVM, deployVMResponse)
-            .put(queryAsyncJobResult, queryAsyncJobResultResponse)
-            .build();
+              .put(listProjects, listProjectsResponse)
+              .put(listTemplates, listTemplatesResponse)
+              .put(listOsTypes, listOsTypesResponse)
+              .put(listOsCategories, listOsCategoriesResponse)
+              .put(listZones, listZonesResponse)
+              .put(listServiceOfferings, listServiceOfferingsResponse)
+              .put(listAccounts, listAccountsResponse)
+              .put(listNetworks, listNetworksResponse)
+              .put(getZone, getZoneResponse)
+              .put(deployVM, deployVMResponse)
+              .put(queryAsyncJobResult, queryAsyncJobResultResponse)
+              .build();
 
       Injector forKeyPair = requestsSendResponses(requestResponseMap);
 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/8b94ee58/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/ProjectApiExpectTest.java
----------------------------------------------------------------------
diff --git 
a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/ProjectApiExpectTest.java
 
b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/ProjectApiExpectTest.java
new file mode 100644
index 0000000..86ad263
--- /dev/null
+++ 
b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/ProjectApiExpectTest.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.cloudstack.features;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Set;
+
+import org.jclouds.cloudstack.CloudStackContext;
+import org.jclouds.cloudstack.domain.Project;
+import org.jclouds.cloudstack.internal.BaseCloudStackExpectTest;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpResponse;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Test the CloudStack ProjectApi
+ *
+ * @author Andrew Bayer
+ */
+@Test(groups = "unit", testName = "ProjectApiExpectTest")
+public class ProjectApiExpectTest extends BaseCloudStackExpectTest<ProjectApi> 
{
+
+
+   public void testListProjectsWhenResponseIs2xx() {
+
+      ProjectApi client = requestSendsResponse(
+         HttpRequest.builder()
+            .method("GET")
+            
.endpoint("http://localhost:8080/client/api?response=json&command=listProjects&listAll=true&apiKey=identity&signature=vtCqaYXfXttr6mD18Es0e22QBIQ%3D";)
+            .addHeader("Accept", "application/json")
+            .build(),
+         HttpResponse.builder()
+            .statusCode(200)
+            .payload(payloadFromResource("/listprojectsresponse.json"))
+            .build());
+
+      Set<Project> projects = ImmutableSet.of(
+              Project.builder()
+                      .id("489da162-0b77-489d-b044-ce39aa018b1f")
+                      .account("thyde")
+                      .displayText("")
+                      .domain("ROOT")
+                      .domainId("41a4917b-7952-499d-ba7f-4c57464d3dc8")
+                      .name("NN-HA-T1")
+                      .state(Project.State.ACTIVE).build(),
+              Project.builder()
+                      .id("1c11f22c-15ac-4fa7-b833-4d748df317b7")
+                      .account("prasadm")
+                      .displayText("Hive")
+                      .domain("ROOT")
+                      .domainId("41a4917b-7952-499d-ba7f-4c57464d3dc8")
+                      .name("hive")
+                      .state(Project.State.ACTIVE).build());
+
+      assertEquals(client.listProjects(), projects);
+   }
+
+   @Override
+   protected ProjectApi clientFrom(CloudStackContext context) {
+      return context.getApi().getProjectApi();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/8b94ee58/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/ProjectApiLiveTest.java
----------------------------------------------------------------------
diff --git 
a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/ProjectApiLiveTest.java
 
b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/ProjectApiLiveTest.java
new file mode 100644
index 0000000..55b9280
--- /dev/null
+++ 
b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/ProjectApiLiveTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.cloudstack.features;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import org.jclouds.cloudstack.domain.Project;
+import org.jclouds.cloudstack.internal.BaseCloudStackApiLiveTest;
+import org.testng.annotations.Test;
+
+/**
+ * Tests behavior of {@code ProjectApi}
+ * 
+ * @author Andrew Bayer
+ */
+@Test(groups = "live", singleThreaded = true, testName = "ProjectApiLiveTest")
+public class ProjectApiLiveTest extends BaseCloudStackApiLiveTest {
+
+   @Test
+   public void testListAccounts() throws Exception {
+      for (Project project : client.getProjectApi().listProjects())
+         checkProject(project);
+   }
+
+   protected void checkProject(Project project) {
+      assertNotNull(project.getId());
+      assertEquals(project.toString(), 
client.getProjectApi().getProject(project.getId()).toString());
+      assertTrue(project.getState() != null);
+      assertTrue(project.getState() != Project.State.UNRECOGNIZED);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/8b94ee58/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/ProjectApiTest.java
----------------------------------------------------------------------
diff --git 
a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/ProjectApiTest.java
 
b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/ProjectApiTest.java
new file mode 100644
index 0000000..e77e844
--- /dev/null
+++ 
b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/ProjectApiTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.cloudstack.features;
+import static org.jclouds.reflect.Reflection2.method;
+
+import java.io.IOException;
+
+import org.jclouds.Fallbacks.EmptySetOnNotFoundOr404;
+import org.jclouds.Fallbacks.NullOnNotFoundOr404;
+import org.jclouds.cloudstack.internal.BaseCloudStackApiTest;
+import org.jclouds.cloudstack.options.ListProjectsOptions;
+import org.jclouds.functions.IdentityFunction;
+import org.jclouds.http.functions.ParseFirstJsonValueNamed;
+import org.jclouds.rest.internal.GeneratedHttpRequest;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Functions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.reflect.Invokable;
+
+/**
+ * Tests behavior of {@code ProjectApi}
+ * 
+ * @author Andrew Bayer
+ */
+// NOTE:without testName, this will not call @Before* and fail w/NPE during
+// surefire
+@Test(groups = "unit", testName = "ProjectApiTest")
+public class ProjectApiTest extends BaseCloudStackApiTest<ProjectApi> {
+
+   public void testListProjects() throws SecurityException, 
NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ProjectApi.class, "listProjects", 
ListProjectsOptions[].class);
+      GeneratedHttpRequest httpRequest = processor.createRequest(method, 
ImmutableList.of());
+
+      assertRequestLineEquals(httpRequest,
+            "GET 
http://localhost:8080/client/api?response=json&command=listProjects&listAll=true
 HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, 
ParseFirstJsonValueNamed.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, EmptySetOnNotFoundOr404.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testListProjectsOptions() throws SecurityException, 
NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ProjectApi.class, "listProjects", 
ListProjectsOptions[].class);
+      GeneratedHttpRequest httpRequest = processor.createRequest(method, 
ImmutableList.<Object> of(
+            ListProjectsOptions.Builder.accountInDomain("jclouds", "123")));
+
+      assertRequestLineEquals(httpRequest,
+            "GET 
http://localhost:8080/client/api?response=json&command=listProjects&listAll=true&account=jclouds&domainid=123
 HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, 
ParseFirstJsonValueNamed.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, EmptySetOnNotFoundOr404.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testGetAccount() throws SecurityException, 
NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ProjectApi.class, "getProject", 
String.class);
+      GeneratedHttpRequest httpRequest = processor.createRequest(method, 
ImmutableList.<Object> of("3"));
+
+      assertRequestLineEquals(httpRequest,
+            "GET 
http://localhost:8080/client/api?response=json&command=listProjects&listAll=true&id=3
 HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest,
+            Functions.compose(IdentityFunction.INSTANCE, 
IdentityFunction.INSTANCE).getClass());
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, NullOnNotFoundOr404.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/8b94ee58/apis/cloudstack/src/test/java/org/jclouds/cloudstack/internal/BaseCloudStackComputeServiceContextExpectTest.java
----------------------------------------------------------------------
diff --git 
a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/internal/BaseCloudStackComputeServiceContextExpectTest.java
 
b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/internal/BaseCloudStackComputeServiceContextExpectTest.java
index 8b8d01d..411651e 100644
--- 
a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/internal/BaseCloudStackComputeServiceContextExpectTest.java
+++ 
b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/internal/BaseCloudStackComputeServiceContextExpectTest.java
@@ -47,7 +47,22 @@ public abstract class 
BaseCloudStackComputeServiceContextExpectTest<T> extends B
    protected final HttpResponse listTemplatesResponse = 
HttpResponse.builder().statusCode(200)
       .payload(payloadFromResource("/listtemplatesresponse.json"))
       .build();
-   
+
+   protected final HttpRequest listProjects = 
HttpRequest.builder().method("GET")
+           .endpoint("http://localhost:8080/client/api";)
+           .addQueryParam("response", "json")
+           .addQueryParam("command", "listProjects")
+           .addQueryParam("listAll", "true")
+           .addQueryParam("account", "jclouds")
+           .addQueryParam("domainid", "457")
+           .addQueryParam("apiKey", "APIKEY")
+           .addQueryParam("signature", "yAx1XbtjeEhdBQCNP0OLyWWAFCw%3D")
+           .addHeader("Accept", "application/json")
+           .build();
+
+   protected final HttpResponse listProjectsResponse = 
HttpResponse.builder().statusCode(200)
+           .build();
+
    protected final HttpRequest listOsTypes = 
HttpRequest.builder().method("GET")
       .endpoint("http://localhost:8080/client/api";)
       .addQueryParam("response", "json")

http://git-wip-us.apache.org/repos/asf/jclouds/blob/8b94ee58/apis/cloudstack/src/test/resources/listprojectsresponse.json
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/test/resources/listprojectsresponse.json 
b/apis/cloudstack/src/test/resources/listprojectsresponse.json
new file mode 100644
index 0000000..33273ef
--- /dev/null
+++ b/apis/cloudstack/src/test/resources/listprojectsresponse.json
@@ -0,0 +1 @@
+{ "listprojectsresponse" : { "count":2 ,"project" : [  
{"id":"489da162-0b77-489d-b044-ce39aa018b1f","name":"NN-HA-T1","displaytext":"","domainid":"41a4917b-7952-499d-ba7f-4c57464d3dc8","domain":"ROOT","account":"thyde","state":"Active"},
 
{"id":"1c11f22c-15ac-4fa7-b833-4d748df317b7","name":"hive","displaytext":"Hive","domainid":"41a4917b-7952-499d-ba7f-4c57464d3dc8","domain":"ROOT","account":"prasadm","state":"Active"}
 ] } }
\ No newline at end of file

Reply via email to