Allow restricting user access by IP address
https://issues.apache.org/jira/browse/JCLOUDS-116
diff --git a/openstack-reddwarf/src/main/java/org/jclouds/openstack/reddwarf/v1/binders/BindCreateUserToJson.java b/openstack-reddwarf/src/main/java/org/jclouds/openstack/reddwarf/v1/binders/BindCreateUserToJson.java
index d03d5e4..a7934ac 100644
--- a/openstack-reddwarf/src/main/java/org/jclouds/openstack/reddwarf/v1/binders/BindCreateUserToJson.java
+++ b/openstack-reddwarf/src/main/java/org/jclouds/openstack/reddwarf/v1/binders/BindCreateUserToJson.java
@@ -21,6 +21,7 @@
 
 import org.jclouds.http.HttpRequest;
 import org.jclouds.openstack.reddwarf.v1.domain.User;
+import org.jclouds.openstack.reddwarf.v1.domain.User.Builder;
 import org.jclouds.rest.MapBinder;
 import org.jclouds.rest.binders.BindToJsonPayload;
 
@@ -42,12 +43,17 @@
       Set<User> users = Sets.newHashSet();
       if( postParams.get("name") != null ) {
          Set<String> databases = Sets.newHashSet();
-         databases.add((String) postParams.get("databaseName"));
-         User user = User.builder()
-               .name((String) postParams.get("name"))
-               .password((String) postParams.get("password"))
-               .databases(databases)
-               .build();
+         if(postParams.get("databaseName")!=null)
+            databases.add((String) postParams.get("databaseName"));
+         
+         Builder builder = User.builder();
+         builder.name((String) postParams.get("name"))
+                .password((String) postParams.get("password"));
+         
+         builder.host((String) postParams.get("host"));
+         builder.databases(databases);
+         
+         User user = builder.build();
          users.add(user);
       }
       else if( postParams.get("users") != null ) {
diff --git a/openstack-reddwarf/src/main/java/org/jclouds/openstack/reddwarf/v1/domain/User.java b/openstack-reddwarf/src/main/java/org/jclouds/openstack/reddwarf/v1/domain/User.java
index 9d9d9f4..27aeef1 100644
--- a/openstack-reddwarf/src/main/java/org/jclouds/openstack/reddwarf/v1/domain/User.java
+++ b/openstack-reddwarf/src/main/java/org/jclouds/openstack/reddwarf/v1/domain/User.java
@@ -36,25 +36,36 @@
 public class User implements Comparable<User>{
    private final String name;
    private final String password;
+   private final String host;
    private final List<Map<String,String>> databases;
 
    @ConstructorProperties({
-      "name", "password", "databases"
+      "name", "password", "host", "databases"
    })
-   protected User(String name, String password, List<Map<String,String>> databases) {
+   protected User(String name, String password, String host, List<Map<String,String>> databases) {
       this.name = checkNotNull(name, "name required");
       this.password = password;
-      if(databases == null)this.databases = Lists.newArrayList();
+      this.host = host;
+      // Set databases to an empty list instead of null
+      if(databases == null) {
+         this.databases = Lists.newArrayList();
+      }
       else {
          this.databases = databases;
       }
    }    
 
-   protected User(String name, String password, Set<String> databases) {
+   protected User(String name, String password, String host, Set<String> databases) {
       this.name = checkNotNull(name, "name required");
       this.password = password;
-      if(databases == null)this.databases = Lists.newArrayList();
+      this.host = host;
+      // Set databases to an empty list instead of null
+      if(databases == null) {
+         this.databases = Lists.newArrayList();
+      }
       else {
+         // Using List<Map<String,String>> as the internal representation makes it easy to serialize properly
+         // with less code; this code is to present databases as List<String> to the user
          List<Map<String,String>> databaseList = Lists.newArrayList();
          for(String databaseName : databases) {
             Map<String,String> singleDatabase = Maps.newHashMap();
@@ -66,13 +77,14 @@
    }   
 
    /**
-    * @return the name of this user
+    * @return the name of this user. The name is not a unique or even sufficient identifier in some cases.
+    * @see User#getIdentifier()
     * @see User.Builder#name(String)
     */
    public String getName() {
       return this.name;
-   }
-
+   }   
+   
    /**
     * @return the password for this user
     * @see User.Builder#password(String)
@@ -80,6 +92,24 @@
    public String getPassword() {
       return this.password;
    }
+   
+   /**
+    * @return the host for this user
+    * @see User.Builder#host(String)
+    */
+   public String getHost() {
+      return this.host;
+   }
+   
+   /**
+    * @return a unique identifier for this user. In most cases, this is just the name. If the user is restricted to connections from a specific host, the hostname must be appended to the user name with a "@"
+    */
+   public String getIdentifier() {
+      if(host==null || "%".equals(host))
+         return name;
+      else 
+         return name + "@" + host;
+   }
 
    /**
     * @return the databases for this user
@@ -105,6 +135,7 @@
       User that = User.class.cast(obj);
       return Objects.equal(this.name, that.name) && 
             Objects.equal(this.password, that.password) &&
+            Objects.equal(this.host, that.host) &&
             Objects.equal(this.databases, that.databases);
    }
 
@@ -112,6 +143,7 @@
       return Objects.toStringHelper(this)
             .add("name", name)
             .add("password", password)
+            .add("host", host)
             .add("databases", databases);
    }
 
@@ -131,8 +163,9 @@
    public static class Builder {
       protected String name;
       protected String password;
+      protected String host;
       protected Set<String> databases;
-
+      
       /** 
        * @param name The name of this user
        * @return The builder object
@@ -152,6 +185,16 @@
          this.password = password;
          return this;
       }
+      
+      /** 
+       * @param host Specifies the host from which a user is allowed to connect to the database. Possible values are a string containing an IPv4 address or "%" to allow connecting from any host. Refer to Section 3.11.1, “User Access Restriction by Host” for details. If host is not specified, it defaults to "%".
+       * @return The builder object
+       * @see User#getHost()
+       */
+      public Builder host(String host) {
+         this.host = host;
+         return this;
+      }
 
       /** 
        * @param name The databases for this user
@@ -168,13 +211,14 @@
        * @return A new User object
        */
       public User build() {
-         return new User(name, password, databases);
+         return new User(name, password, host, databases);
       }
 
       public Builder fromUser(User in) {
          return this
                .name(in.getName())
                .password(in.getPassword())
+               .host(in.getHost())
                .databases(ImmutableSet.copyOf( in.getDatabases() ));
       }        
    }
diff --git a/openstack-reddwarf/src/main/java/org/jclouds/openstack/reddwarf/v1/features/UserApi.java b/openstack-reddwarf/src/main/java/org/jclouds/openstack/reddwarf/v1/features/UserApi.java
index 4a91457..9fd9a95 100644
--- a/openstack-reddwarf/src/main/java/org/jclouds/openstack/reddwarf/v1/features/UserApi.java
+++ b/openstack-reddwarf/src/main/java/org/jclouds/openstack/reddwarf/v1/features/UserApi.java
@@ -92,6 +92,23 @@
    @Fallback(FalseOnNotFoundOr404.class)
    @MapBinder(BindCreateUserToJson.class)
    boolean create(@PayloadParam("name") String userName, @PayloadParam("password") String password, @PayloadParam("databaseName") String databaseName);
+   
+   /**
+    * Create a database user by name, password, and database name. Simpler overload for {@link #create(String, Set)} 
+    *
+    * @param userName Name of the user for the database.
+    * @param password User password for database access.
+    * @param host Specifies the host from which a user is allowed to connect to the database. Possible values are a string containing an IPv4 address or "%" to allow connecting from any host. Refer to Section 3.11.1, “User Access Restriction by Host” for details. If host is not specified, it defaults to "%".
+    * @param databaseName Name of the database that the user can access.
+    * @return true if successful
+    */
+   @Named("user:create")
+   @POST
+   @Path("/users")
+   @Consumes(MediaType.APPLICATION_JSON)
+   @Fallback(FalseOnNotFoundOr404.class)
+   @MapBinder(BindCreateUserToJson.class)
+   boolean create(@PayloadParam("name") String userName, @PayloadParam("password") String password, @PayloadParam("host") String host, @PayloadParam("databaseName") String databaseName);
 
    /**
     * This operation grants access for the specified user to a database for the specified instance.
@@ -174,7 +191,7 @@
     * @param userName The name for the specified user.
     * @return The list of Users
     */
-   @Named("user:getDatabaseList/{instanceId}/{name}")
+   @Named("user:getDatabaseList/{name}")
    @GET
    @Path("/users/{name}/databases")
    @ResponseParser(ParseDatabaseListForUser.class)
@@ -183,18 +200,33 @@
    FluentIterable<String> getDatabaseList(@PathParam("name") String userName);
       
    /**
-    * Returns a User by name
+    * Returns a User by identifier
     *
-    * @param instanceId The instance ID for the specified database instance.
-    * @param userName The name for the specified user.
+    * @param name The name or identifier for the specified user.
     * @return User or Null on not found
     */
-   @Named("user:list/{instanceId}/{userName}")
+   @Named("user:get/{name}")
    @GET
    @Path("/users/{name}")
    @SelectJson("user")
    @Consumes(MediaType.APPLICATION_JSON)
    @Fallback(NullOnNotFoundOr404.class)
    @Nullable
-   User get(@PathParam("name") String userName);
+   User get(@PathParam("name") String name);
+   
+   /**
+    * Returns a User by name and allowed host
+    *
+    * @param name The name for the specified user.
+    * @param host The associated hostname
+    * @return User or Null on not found
+    */
+   @Named("user:get/{name}@{hostname}")
+   @GET
+   @Path("/users/{name}@{hostname}")
+   @SelectJson("user")
+   @Consumes(MediaType.APPLICATION_JSON)
+   @Fallback(NullOnNotFoundOr404.class)
+   @Nullable
+   User get(@PathParam("name") String name, @PathParam("hostname") String hostname);
 }
diff --git a/openstack-reddwarf/src/test/java/org/jclouds/openstack/reddwarf/v1/features/DatabaseApiExpectTest.java b/openstack-reddwarf/src/test/java/org/jclouds/openstack/reddwarf/v1/features/DatabaseApiExpectTest.java
index 8e308e7..a971a2f 100644
--- a/openstack-reddwarf/src/test/java/org/jclouds/openstack/reddwarf/v1/features/DatabaseApiExpectTest.java
+++ b/openstack-reddwarf/src/test/java/org/jclouds/openstack/reddwarf/v1/features/DatabaseApiExpectTest.java
@@ -156,6 +156,6 @@
       ).getDatabaseApiForInstanceInZone("instanceId-1234-5678","RegionOne");

 

       Set<String> databases = api.list().toSet();

-      assertEquals(databases.size(), 0);

+      assertTrue(databases.isEmpty());

    }

 }

diff --git a/openstack-reddwarf/src/test/java/org/jclouds/openstack/reddwarf/v1/features/DatabaseApiLiveTest.java b/openstack-reddwarf/src/test/java/org/jclouds/openstack/reddwarf/v1/features/DatabaseApiLiveTest.java
index 8aa7dac..d319f4b 100644
--- a/openstack-reddwarf/src/test/java/org/jclouds/openstack/reddwarf/v1/features/DatabaseApiLiveTest.java
+++ b/openstack-reddwarf/src/test/java/org/jclouds/openstack/reddwarf/v1/features/DatabaseApiLiveTest.java
@@ -51,8 +51,8 @@
          // create instances
          List<Instance> instanceList = Lists.newArrayList();
          InstanceApi instanceApi = api.getInstanceApiForZone(zone);
-         Instance first = instanceApi.create("1", 1, "first_database_testing");
-         Instance second = instanceApi.create("1", 1, "second_database_testing");
+         Instance first = instanceApi.create("1", 1, "first_database_testing_" + zone);
+         Instance second = instanceApi.create("1", 1, "second_database_testing_" + zone);
          instanceList.add(first);
          instanceList.add(second);
          InstancePredicates.awaitAvailable(instanceApi).apply(first);
diff --git a/openstack-reddwarf/src/test/java/org/jclouds/openstack/reddwarf/v1/features/InstanceApiLiveTest.java b/openstack-reddwarf/src/test/java/org/jclouds/openstack/reddwarf/v1/features/InstanceApiLiveTest.java
index 9d05956..18bbcec 100644
--- a/openstack-reddwarf/src/test/java/org/jclouds/openstack/reddwarf/v1/features/InstanceApiLiveTest.java
+++ b/openstack-reddwarf/src/test/java/org/jclouds/openstack/reddwarf/v1/features/InstanceApiLiveTest.java
@@ -21,6 +21,7 @@
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.assertFalse;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -37,7 +38,7 @@
 /**
  * @author Zack Shoylev
  */
-@Test(groups = "live", testName = "InstanceApiLiveTest")
+@Test(groups = "live", testName = "InstanceApiLiveTest", singleThreaded = true)
 public class InstanceApiLiveTest extends BaseRedDwarfApiLiveTest {
 
     private static Map<String,List<Instance>> created = Maps.newHashMap();
@@ -49,8 +50,8 @@
         for (String zone : api.getConfiguredZones()) {
             List<Instance> zoneList = Lists.newArrayList();
             InstanceApi instanceApi = api.getInstanceApiForZone(zone);
-            zoneList.add(instanceApi.create("1", 1, "first_instance_testing"));
-            Instance second = instanceApi.create("1", 1, "second_instance_testing");
+            zoneList.add(instanceApi.create("1", 1, "first_instance_testing_" + zone));
+            Instance second = instanceApi.create("1", 1, "second_instance_testing_" + zone);
             InstancePredicates.awaitAvailable(instanceApi).apply(second);
             instanceApi.enableRoot(second.getId());
             zoneList.add(second);            
@@ -81,7 +82,7 @@
         for (String zone : api.getConfiguredZones()) {
             InstanceApi instanceApi = api.getInstanceApiForZone(zone);
             FluentIterable<Instance> response = instanceApi.list(); 
-            assertTrue(response.size() > 0 );
+            assertFalse(response.isEmpty());
             for (Instance instance : response) {
                 checkInstance(instance);
             }  
diff --git a/openstack-reddwarf/src/test/java/org/jclouds/openstack/reddwarf/v1/features/UserApiExpectTest.java b/openstack-reddwarf/src/test/java/org/jclouds/openstack/reddwarf/v1/features/UserApiExpectTest.java
index 484b087..b2e785a 100644
--- a/openstack-reddwarf/src/test/java/org/jclouds/openstack/reddwarf/v1/features/UserApiExpectTest.java
+++ b/openstack-reddwarf/src/test/java/org/jclouds/openstack/reddwarf/v1/features/UserApiExpectTest.java
@@ -76,6 +76,38 @@
       boolean result = api.create("dbuser1", "password", "databaseA");

       assertFalse(result);

    }

+   

+   public void testCreateUserSimpleWithHost() {

+      URI endpoint = URI.create("http://172.16.0.1:8776/v1/3456/instances/instanceId-1234-5678/users");

+      UserApi api = requestsSendResponses(

+            keystoneAuthWithUsernameAndPasswordAndTenantName,

+            responseWithKeystoneAccess,

+            authenticatedGET().endpoint(endpoint) // bad naming convention, you should not be able to change the method to POST

+            .method("POST")

+            .payload(payloadFromResourceWithContentType("/user_create_with_host_simple_request.json", MediaType.APPLICATION_JSON))

+            .build(),

+            HttpResponse.builder().statusCode(202).build() // response

+            ).getUserApiForInstanceInZone("instanceId-1234-5678","RegionOne");

+

+      boolean result = api.create("dbuser1", "password", "192.168.64.64", "databaseA");

+      assertTrue(result);

+   }

+   

+   public void testCreateUserSimpleWithHostFail() {

+      URI endpoint = URI.create("http://172.16.0.1:8776/v1/3456/instances/instanceId-1234-5678/users");

+      UserApi api = requestsSendResponses(

+            keystoneAuthWithUsernameAndPasswordAndTenantName,

+            responseWithKeystoneAccess,

+            authenticatedGET().endpoint(endpoint) // bad naming convention, you should not be able to change the method to POST

+            .method("POST")

+            .payload(payloadFromResourceWithContentType("/user_create_with_host_simple_request.json", MediaType.APPLICATION_JSON))

+            .build(),

+            HttpResponse.builder().statusCode(404).build() // response

+            ).getUserApiForInstanceInZone("instanceId-1234-5678","RegionOne");

+

+      boolean result = api.create("dbuser1", "password", "192.168.64.64", "databaseA");

+      assertFalse(result);

+   }

 

    public void testCreateUser() {

       URI endpoint = URI.create("http://172.16.0.1:8776/v1/3456/instances/instanceId-1234-5678/users");

@@ -99,7 +131,7 @@
       databases3.add( "databaseD" );

       User user1 = User.builder().databases( databases1 ).name("dbuser1").password("password").build();

       User user2 = User.builder().databases( databases2 ).name("dbuser2").password("password").build();

-      User user3 = User.builder().databases( databases3 ).name("dbuser3").password("password").build();

+      User user3 = User.builder().databases( databases3 ).name("dbuser3").password("password").host("192.168.64.64").build();

       Set<User> users = Sets.newHashSet();

       users.add(user1);

       users.add(user2);

@@ -131,7 +163,7 @@
       databases3.add( "databaseD" );

       User user1 = User.builder().databases( databases1 ).name("dbuser1").password("password").build();

       User user2 = User.builder().databases( databases2 ).name("dbuser2").password("password").build();

-      User user3 = User.builder().databases( databases3 ).name("dbuser3").password("password").build();

+      User user3 = User.builder().databases( databases3 ).name("dbuser3").password("password").host("192.168.64.64").build();

       Set<User> users = Sets.newHashSet();

       users.add(user1);

       users.add(user2);

@@ -296,7 +328,7 @@
 

       Set<User> users = api.list().toSet();

       assertEquals(users.size(), 4);

-      assertEquals(users.iterator().next().getDatabases().size(), 0);

+      assertTrue(users.iterator().next().getDatabases().isEmpty());

       assertEquals(users.iterator().next().getName(), "dbuser1");

    }

    

@@ -310,7 +342,7 @@
       ).getUserApiForInstanceInZone("instanceId-1234-5678","RegionOne");

 

       Set<User> users = api.list().toSet();

-      assertEquals(users.size(), 0);

+      assertTrue(users.isEmpty());

    }

    

    public void testUserGetDatabaseList() {

@@ -337,7 +369,7 @@
       ).getUserApiForInstanceInZone("instanceId-1234-5678","RegionOne");

 

       Set<String> databases = api.getDatabaseList("dbuser1").toSet();

-      assertEquals(databases.size(), 0);

+      assertTrue(databases.isEmpty());

    }

    

    public void testGetUser() {

@@ -351,6 +383,7 @@
 

       User user = api.get("exampleuser");

       assertEquals(user.getName(), "exampleuser");

+      assertEquals(user.getHost(), "%");

       assertEquals(user.getDatabases().size(), 2);

       assertEquals(user.getDatabases().iterator().next(), "databaseA");

    }

@@ -367,4 +400,34 @@
       User user = api.get("exampleuser");

       assertNull(user);

    }

+   

+   public void testGetUserWithHostname() {

+      URI endpoint = URI.create("http://172.16.0.1:8776/v1/3456/instances/instanceId-1234-5678/users/exampleuser%40192.168.64.64");

+      UserApi api = requestsSendResponses(

+            keystoneAuthWithUsernameAndPasswordAndTenantName,

+            responseWithKeystoneAccess,

+            authenticatedGET().endpoint(endpoint).build(),

+            HttpResponse.builder().statusCode(200).payload(payloadFromResource("/user_get_withhost.json")).build()

+      ).getUserApiForInstanceInZone("instanceId-1234-5678","RegionOne");

+

+      User user = api.get("exampleuser", "192.168.64.64");

+      assertEquals(user.getName(), "exampleuser");

+      assertEquals(user.getHost(), "192.168.64.64");

+      assertEquals(user.getIdentifier(), "exampleuser@192.168.64.64");

+      assertEquals(user.getDatabases().size(), 2);

+      assertEquals(user.getDatabases().iterator().next(), "databaseA");

+   }

+   

+   public void testGetUserWithHostnameFail() {

+      URI endpoint = URI.create("http://172.16.0.1:8776/v1/3456/instances/instanceId-1234-5678/users/exampleuser%40192.168.64.64");

+      UserApi api = requestsSendResponses(

+            keystoneAuthWithUsernameAndPasswordAndTenantName,

+            responseWithKeystoneAccess,

+            authenticatedGET().endpoint(endpoint).build(),

+            HttpResponse.builder().statusCode(404).payload(payloadFromResource("/user_get_withhost.json")).build()

+      ).getUserApiForInstanceInZone("instanceId-1234-5678","RegionOne");

+

+      User user = api.get("exampleuser", "192.168.64.64");

+      assertNull(user);

+   }

 }

diff --git a/openstack-reddwarf/src/test/java/org/jclouds/openstack/reddwarf/v1/features/UserApiLiveTest.java b/openstack-reddwarf/src/test/java/org/jclouds/openstack/reddwarf/v1/features/UserApiLiveTest.java
index d9a4e7e..3c539d0 100644
--- a/openstack-reddwarf/src/test/java/org/jclouds/openstack/reddwarf/v1/features/UserApiLiveTest.java
+++ b/openstack-reddwarf/src/test/java/org/jclouds/openstack/reddwarf/v1/features/UserApiLiveTest.java
@@ -59,8 +59,8 @@
          // create instances
          List<Instance> instanceList = Lists.newArrayList();
          InstanceApi instanceApi = api.getInstanceApiForZone(zone);
-         Instance first = instanceApi.create("1", 1, "first_user_testing");
-         Instance second = instanceApi.create("1", 1, "second_user_testing");
+         Instance first = instanceApi.create("1", 1, "first_user_reddwarf_live_testing_" + zone);
+         Instance second = instanceApi.create("1", 1, "second_user_reddwarf_live_testing_" + zone);
          instanceList.add(first);
          instanceList.add(second);
          InstancePredicates.awaitAvailable(instanceApi).apply(first);
@@ -72,19 +72,20 @@
                .password(UUID.randomUUID().toString())
                .databases(ImmutableSet.of(
                      "u1db1", 
-                     "u1db1")).build();
+                     "u1db2")).build();
          User user2 = User.builder()
                .name("user2")
                .password(UUID.randomUUID().toString())
                .databases(ImmutableSet.of(
                      "u2db1", 
-                     "u2db1")).build();
+                     "u2db2")).build();
          User user3 = User.builder()
                .name("user3")
                .password(UUID.randomUUID().toString())
+               .host("173.203.44.122")
                .databases(ImmutableSet.of(
                      "u3db1", 
-                     "u3db1")).build();
+                     "u3db2")).build();
          UserApi userApiFirst = api.getUserApiForInstanceInZone(first.getId(), zone);
          UserApi userApiSecond = api.getUserApiForInstanceInZone(second.getId(), zone);
          userApiFirst.create(ImmutableSet.of(user1, user2));
@@ -107,7 +108,8 @@
 
    private void checkUser(User user) {
       assertNotNull(user.getName(), "Name cannot be null for " + user);
-      checkArgument(user.getDatabases().size() > 0, "Number of databases must not be 0");
+      assertNotNull(user.getHost(), "Host cannot be null (should be '%' if default) for " + user);
+      checkArgument(!user.getDatabases().isEmpty(), "Number of databases must not be 0");
    }
 
    @Test
@@ -117,7 +119,7 @@
          assertTrue(instanceApi.list().size() >= 2);
          for(Instance instance : instanceApi.list() ) {
             UserApi userApi = api.getUserApiForInstanceInZone(instance.getId(), zone);
-            if(!instance.getName().contains("user_testing"))continue;
+            if(!instance.getName().contains("user_reddwarf_live_testing"))continue;
             assertTrue(userApi.list().size() >=1);
             for(User user : userApi.list()){
                checkUser(user);      
@@ -133,12 +135,15 @@
          assertTrue(instanceApi.list().size() >= 2);
          for(Instance instance : instanceApi.list() ) {
             UserApi userApi = api.getUserApiForInstanceInZone(instance.getId(), zone);
-            if(!instance.getName().contains("user_testing"))continue;
+            if(!instance.getName().contains("user_reddwarf_live_testing"))continue;
             assertTrue(userApi.list().size() >=1);
             for(User user : userApi.list()){
-               User userFromGet = userApi.get(user.getName());
+               User userFromGet = userApi.get(user.getIdentifier());
                assertEquals(userFromGet.getName(), user.getName());
-               assertEquals(userFromGet.getDatabases(), user.getDatabases());     
+               assertEquals(userFromGet.getHost(), user.getHost());
+               assertEquals(userFromGet.getIdentifier(), user.getIdentifier());
+               assertEquals(userFromGet.getDatabases(), user.getDatabases());
+               assertEquals(userFromGet, user);
             }
          }  
       } 
@@ -151,10 +156,10 @@
          assertTrue(instanceApi.list().size() >= 2 );
          for(Instance instance : instanceApi.list() ) {
             UserApi userApi = api.getUserApiForInstanceInZone(instance.getId(), zone);
-            if(!instance.getName().contains("user_testing"))continue;
+            if(!instance.getName().contains("user_reddwarf_live_testing"))continue;
             assertTrue(userApi.list().size() >=1);
             for(User user : userApi.list()){
-               assertTrue(userApi.getDatabaseList(user.getName()).size()>0);
+               assertFalse(userApi.getDatabaseList(user.getIdentifier()).isEmpty());
             }
          }  
       } 
@@ -167,24 +172,24 @@
          assertTrue(instanceApi.list().size() >= 2);
          for(Instance instance : instanceApi.list() ) {
             UserApi userApi = api.getUserApiForInstanceInZone(instance.getId(), zone);
-            if(!instance.getName().contains("user_testing"))continue;
+            if(!instance.getName().contains("user_reddwarf_live_testing"))continue;
             assertTrue(userApi.list().size() >=1);
             for(User user : userApi.list()){
-               userApi.grant(user.getName(), "dbA");
-               userApi.grant(user.getName(), ImmutableList.of(
+               userApi.grant(user.getIdentifier(), "dbA");
+               userApi.grant(user.getIdentifier(), ImmutableList.of(
                      "dbB", 
                      "dbC"));
 
-               Set<String> databases = userApi.getDatabaseList(user.getName()).toSet();
+               Set<String> databases = userApi.getDatabaseList(user.getIdentifier()).toSet();
                assertTrue(databases.contains("dbA"));
                assertTrue(databases.contains("dbB"));
                assertTrue(databases.contains("dbC"));
 
-               userApi.revoke(user.getName(), "dbA");
-               userApi.revoke(user.getName(), "dbB");
-               userApi.revoke(user.getName(), "dbC");
+               userApi.revoke(user.getIdentifier(), "dbA");
+               userApi.revoke(user.getIdentifier(), "dbB");
+               userApi.revoke(user.getIdentifier(), "dbC");
 
-               databases = userApi.getDatabaseList(user.getName()).toSet();
+               databases = userApi.getDatabaseList(user.getIdentifier()).toSet();
                assertFalse(databases.contains("dbA"));
                assertFalse(databases.contains("dbB"));
                assertFalse(databases.contains("dbC"));
diff --git a/openstack-reddwarf/src/test/java/org/jclouds/openstack/reddwarf/v1/parse/ParseUserListTest.java b/openstack-reddwarf/src/test/java/org/jclouds/openstack/reddwarf/v1/parse/ParseUserListTest.java
index 985368c..e328de6 100644
--- a/openstack-reddwarf/src/test/java/org/jclouds/openstack/reddwarf/v1/parse/ParseUserListTest.java
+++ b/openstack-reddwarf/src/test/java/org/jclouds/openstack/reddwarf/v1/parse/ParseUserListTest.java
@@ -48,15 +48,17 @@
       return ImmutableSet
             .of(User.builder()
                   .name("dbuser1")
+                  .host("%")
                   .build(),
                   User.builder()
                   .name("dbuser2")
+                  .host("%")
                   .databases( ImmutableSet.of( 
                           "databaseB",
                           "databaseC") )
                   .build(),
-                  User.builder().name("dbuser3").build(),
-                  User.builder().name("demouser").databases(
+                  User.builder().name("dbuser3").host("%").build(),
+                  User.builder().name("demouser").host("%").databases(
                           ImmutableSet.of("sampledb"))
                           .build()
                   );
diff --git a/openstack-reddwarf/src/test/resources/logback-test.xml b/openstack-reddwarf/src/test/resources/logback-test.xml
new file mode 100644
index 0000000..6559c23
--- /dev/null
+++ b/openstack-reddwarf/src/test/resources/logback-test.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0"?>
+<!--
+
+    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.
+
+-->
+<configuration scan="false">
+    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
+        <file>target/test-data/jclouds.log</file>
+
+        <encoder>
+            <Pattern>%d %-5p [%c] [%thread] %m%n</Pattern>
+        </encoder>
+    </appender>
+
+    <appender name="WIREFILE" class="ch.qos.logback.core.FileAppender">
+        <file>target/test-data/jclouds-wire.log</file>
+
+        <encoder>
+            <Pattern>%d %-5p [%c] [%thread] %m%n</Pattern>
+        </encoder>
+    </appender>
+
+    <appender name="BLOBSTOREFILE" class="ch.qos.logback.core.FileAppender">
+        <file>target/test-data/jclouds-blobstore.log</file>
+
+        <encoder>
+            <Pattern>%d %-5p [%c] [%thread] %m%n</Pattern>
+        </encoder>
+    </appender>
+
+    <root>
+        <level value="warn" />
+    </root>
+
+    <logger name="org.jclouds">
+        <level value="DEBUG" />
+        <appender-ref ref="FILE" />
+    </logger>
+
+    <logger name="jclouds.wire">
+        <level value="DEBUG" />
+        <appender-ref ref="WIREFILE" />
+    </logger>
+
+    <logger name="jclouds.headers">
+        <level value="DEBUG" />
+        <appender-ref ref="WIREFILE" />
+    </logger>
+
+    <logger name="jclouds.blobstore">
+        <level value="DEBUG" />
+        <appender-ref ref="BLOBSTOREFILE" />
+    </logger>
+
+</configuration>
diff --git a/openstack-reddwarf/src/test/resources/reddwarf_user_list.json b/openstack-reddwarf/src/test/resources/reddwarf_user_list.json
index 55afb56..546df43 100644
--- a/openstack-reddwarf/src/test/resources/reddwarf_user_list.json
+++ b/openstack-reddwarf/src/test/resources/reddwarf_user_list.json
@@ -1,7 +1,8 @@
 {
     "users": [
         {
-            "databases": [], 
+            "databases": [],
+            "host": "%", 
             "name": "dbuser1"
         }, 
         {
@@ -12,19 +13,22 @@
                 {
                     "name": "databaseC"
                 }
-            ], 
+            ],
+            "host": "%",
             "name": "dbuser2"
         }, 
         {
             "databases": [], 
-            "name": "dbuser3"
+            "name": "dbuser3",
+            "host": "%"
         }, 
         {
             "databases": [
                 {
                     "name": "sampledb"
                 }
-            ], 
+            ],
+            "host": "%",
             "name": "demouser"
         }
     ]
diff --git a/openstack-reddwarf/src/test/resources/user_create_request.json b/openstack-reddwarf/src/test/resources/user_create_request.json
index 7742898..f9b923e 100644
--- a/openstack-reddwarf/src/test/resources/user_create_request.json
+++ b/openstack-reddwarf/src/test/resources/user_create_request.json
@@ -28,6 +28,7 @@
                 }

             ],

             "name":"dbuser3",

+            "host":"192.168.64.64",

             "password":"password"

         }

     ]

diff --git a/openstack-reddwarf/src/test/resources/user_create_with_host_simple_request.json b/openstack-reddwarf/src/test/resources/user_create_with_host_simple_request.json
new file mode 100644
index 0000000..5dc0b78
--- /dev/null
+++ b/openstack-reddwarf/src/test/resources/user_create_with_host_simple_request.json
@@ -0,0 +1,14 @@
+{

+    "users":[

+        {

+            "databases":[

+                {

+                    "name":"databaseA"

+                }

+            ],

+            "name":"dbuser1",

+            "password":"password",

+            "host":"192.168.64.64"

+        }

+    ]

+}

diff --git a/openstack-reddwarf/src/test/resources/user_get.json b/openstack-reddwarf/src/test/resources/user_get.json
index 8d4169c..478a94d 100644
--- a/openstack-reddwarf/src/test/resources/user_get.json
+++ b/openstack-reddwarf/src/test/resources/user_get.json
@@ -1,6 +1,7 @@
 {
    "user": {
       "name": "exampleuser",
+      "host": "%",
       "databases": [
          {
             "name": "databaseA"
diff --git a/openstack-reddwarf/src/test/resources/user_get_withhost.json b/openstack-reddwarf/src/test/resources/user_get_withhost.json
new file mode 100644
index 0000000..03274b6
--- /dev/null
+++ b/openstack-reddwarf/src/test/resources/user_get_withhost.json
@@ -0,0 +1,14 @@
+{
+   "user": {
+      "name": "exampleuser",
+      "host": "192.168.64.64",
+      "databases": [
+         {
+            "name": "databaseA"
+         },
+         {
+            "name": "databaseB"
+         }
+      ]
+   }
+}