Access functions for users
diff --git a/rackspace/db/v1/users/fixtures.go b/rackspace/db/v1/users/fixtures.go
new file mode 100644
index 0000000..9fdae69
--- /dev/null
+++ b/rackspace/db/v1/users/fixtures.go
@@ -0,0 +1,119 @@
+package users
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+const singleDB = `{"databases": [{"name": "databaseE"}]}`
+
+func setupHandler(t *testing.T, url, method, requestBody, responseBody string, status int) {
+	th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, method)
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+		if requestBody != "" {
+			th.TestJSONRequest(t, r, requestBody)
+		}
+
+		w.WriteHeader(status)
+
+		if responseBody != "" {
+			w.Header().Add("Content-Type", "application/json")
+			fmt.Fprintf(w, responseBody)
+		}
+	})
+}
+
+func HandleChangePasswordSuccessfully(t *testing.T, instanceID string) {
+	th.Mux.HandleFunc("/instances/"+instanceID+"/users", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "PUT")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestJSONRequest(t, r, `
+{
+  "users": [
+    {
+      "name": "dbuser1",
+      "password": "newpassword"
+    },
+    {
+      "name": "dbuser2",
+      "password": "anotherpassword"
+    }
+  ]
+}
+`)
+
+		w.Header().Set("Content-Type", "application/json")
+		w.WriteHeader(http.StatusAccepted)
+	})
+}
+
+func HandleUpdateSuccessfully(t *testing.T, instanceID, userName string) {
+	th.Mux.HandleFunc("/instances/"+instanceID+"/users/"+userName, func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "PUT")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestJSONRequest(t, r, `
+{
+  "user": {
+    "name": "new_username",
+    "password": "new_password"
+  }
+}
+`)
+
+		w.Header().Set("Content-Type", "application/json")
+		w.WriteHeader(http.StatusAccepted)
+	})
+}
+
+func HandleGetSuccessfully(t *testing.T, instanceID, userName string) {
+	th.Mux.HandleFunc("/instances/"+instanceID+"/users/"+userName, func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+		w.Header().Add("Content-Type", "application/json")
+
+		fmt.Fprintf(w, `
+{
+  "user": {
+    "name": "exampleuser",
+    "host": "foo",
+    "databases": [
+      {
+        "name": "databaseA"
+      },
+      {
+        "name": "databaseB"
+      }
+    ]
+  }
+}
+`)
+	})
+}
+
+func HandleListUserAccessSuccessfully(t *testing.T, instanceID, userName string) {
+	th.Mux.HandleFunc("/instances/"+instanceID+"/users/"+userName+"/databases", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+		w.Header().Add("Content-Type", "application/json")
+
+		fmt.Fprintf(w, singleDB)
+	})
+}
+
+func HandleGrantUserAccessSuccessfully(t *testing.T, instanceID, userName string) {
+	url := "/instances/" + instanceID + "/users/" + userName + "/databases"
+	setupHandler(t, url, "PUT", singleDB, "", http.StatusAccepted)
+}
+
+func HandleRevokeUserAccessSuccessfully(t *testing.T, instanceID, userName, dbName string) {
+	url := "/instances/" + instanceID + "/users/" + userName + "/databases/" + dbName
+	setupHandler(t, url, "DELETE", "", "", http.StatusAccepted)
+}
diff --git a/rackspace/db/v1/users/requests.go b/rackspace/db/v1/users/requests.go
new file mode 100644
index 0000000..18303e9
--- /dev/null
+++ b/rackspace/db/v1/users/requests.go
@@ -0,0 +1,95 @@
+package users
+
+import (
+	"github.com/racker/perigee"
+	"github.com/rackspace/gophercloud"
+	db "github.com/rackspace/gophercloud/openstack/db/v1/databases"
+	os "github.com/rackspace/gophercloud/openstack/db/v1/users"
+	"github.com/rackspace/gophercloud/pagination"
+)
+
+func ChangePassword(client *gophercloud.ServiceClient, instanceID string, opts os.BatchCreateOpts) UpdatePasswordsResult {
+	var res UpdatePasswordsResult
+
+	reqBody, err := opts.ToUserCreateMap()
+	if err != nil {
+		res.Err = err
+		return res
+	}
+
+	_, res.Err = perigee.Request("PUT", baseURL(client, instanceID), perigee.Options{
+		MoreHeaders: client.AuthenticatedHeaders(),
+		ReqBody:     &reqBody,
+		OkCodes:     []int{202},
+	})
+
+	return res
+}
+
+func Update(client *gophercloud.ServiceClient, instanceID, userName string, opts os.CreateOpts) UpdateResult {
+	var res UpdateResult
+
+	reqBody, err := opts.ToMap()
+	if err != nil {
+		res.Err = err
+		return res
+	}
+	reqBody = map[string]interface{}{"user": reqBody}
+
+	_, res.Err = perigee.Request("PUT", userURL(client, instanceID, userName), perigee.Options{
+		MoreHeaders: client.AuthenticatedHeaders(),
+		ReqBody:     &reqBody,
+		OkCodes:     []int{202},
+	})
+
+	return res
+}
+
+func Get(client *gophercloud.ServiceClient, instanceID, userName string) GetResult {
+	var res GetResult
+
+	_, res.Err = perigee.Request("GET", userURL(client, instanceID, userName), perigee.Options{
+		MoreHeaders: client.AuthenticatedHeaders(),
+		Results:     &res.Body,
+		OkCodes:     []int{200},
+	})
+
+	return res
+}
+
+func ListAccess(client *gophercloud.ServiceClient, instanceID, userName string) pagination.Pager {
+	pageFn := func(r pagination.PageResult) pagination.Page {
+		return AccessPage{pagination.LinkedPageBase{PageResult: r}}
+	}
+
+	return pagination.NewPager(client, dbsURL(client, instanceID, userName), pageFn)
+}
+
+func GrantAccess(client *gophercloud.ServiceClient, instanceID, userName string, opts db.BatchCreateOpts) GrantAccessResult {
+	var res GrantAccessResult
+
+	reqBody, err := opts.ToDBCreateMap()
+	if err != nil {
+		res.Err = err
+		return res
+	}
+
+	_, res.Err = perigee.Request("PUT", dbsURL(client, instanceID, userName), perigee.Options{
+		MoreHeaders: client.AuthenticatedHeaders(),
+		ReqBody:     &reqBody,
+		OkCodes:     []int{202},
+	})
+
+	return res
+}
+
+func RevokeAccess(client *gophercloud.ServiceClient, instanceID, userName, dbName string) RevokeAccessResult {
+	var res RevokeAccessResult
+
+	_, res.Err = perigee.Request("DELETE", dbURL(client, instanceID, userName, dbName), perigee.Options{
+		MoreHeaders: client.AuthenticatedHeaders(),
+		OkCodes:     []int{202},
+	})
+
+	return res
+}
diff --git a/rackspace/db/v1/users/requests_test.go b/rackspace/db/v1/users/requests_test.go
new file mode 100644
index 0000000..17de21d
--- /dev/null
+++ b/rackspace/db/v1/users/requests_test.go
@@ -0,0 +1,122 @@
+package users
+
+import (
+	"testing"
+
+	db "github.com/rackspace/gophercloud/openstack/db/v1/databases"
+	os "github.com/rackspace/gophercloud/openstack/db/v1/users"
+	"github.com/rackspace/gophercloud/pagination"
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+const userName = "{userName}"
+
+func TestChangeUserPassword(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	HandleChangePasswordSuccessfully(t, instanceID)
+
+	opts := os.BatchCreateOpts{
+		os.CreateOpts{Name: "dbuser1", Password: "newpassword"},
+		os.CreateOpts{Name: "dbuser2", Password: "anotherpassword"},
+	}
+
+	err := ChangePassword(fake.ServiceClient(), instanceID, opts).ExtractErr()
+
+	th.AssertNoErr(t, err)
+}
+
+func TestUpdateUser(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	HandleUpdateSuccessfully(t, instanceID, userName)
+
+	opts := os.CreateOpts{
+		Name:     "new_username",
+		Password: "new_password",
+	}
+
+	err := Update(fake.ServiceClient(), instanceID, userName, opts).ExtractErr()
+
+	th.AssertNoErr(t, err)
+}
+
+func TestGetUser(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	HandleGetSuccessfully(t, instanceID, userName)
+
+	user, err := Get(fake.ServiceClient(), instanceID, userName).Extract()
+
+	th.AssertNoErr(t, err)
+
+	expected := &User{
+		Name: "exampleuser",
+		Host: "foo",
+		Databases: []db.Database{
+			db.Database{Name: "databaseA"},
+			db.Database{Name: "databaseB"},
+		},
+	}
+
+	th.AssertDeepEquals(t, expected, user)
+}
+
+func TestUserAccessList(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	HandleListUserAccessSuccessfully(t, instanceID, userName)
+
+	expectedDBs := []db.Database{
+		db.Database{Name: "databaseE"},
+	}
+
+	pages := 0
+	err := ListAccess(fake.ServiceClient(), instanceID, userName).EachPage(func(page pagination.Page) (bool, error) {
+		pages++
+
+		actual, err := ExtractDBs(page)
+		if err != nil {
+			return false, err
+		}
+
+		th.CheckDeepEquals(t, expectedDBs, actual)
+
+		return true, nil
+	})
+
+	th.AssertNoErr(t, err)
+
+	if pages != 1 {
+		t.Errorf("Expected 1 page, saw %d", pages)
+	}
+}
+
+func TestGrantAccess(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	HandleGrantUserAccessSuccessfully(t, instanceID, userName)
+
+	opts := db.BatchCreateOpts{
+		db.CreateOpts{Name: "databaseE"},
+	}
+
+	err := GrantAccess(fake.ServiceClient(), instanceID, userName, opts).ExtractErr()
+	th.AssertNoErr(t, err)
+}
+
+func TestRevokeAccess(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	HandleRevokeUserAccessSuccessfully(t, instanceID, userName, "{dbName}")
+
+	err := RevokeAccess(fake.ServiceClient(), instanceID, userName, "{dbName}").ExtractErr()
+	th.AssertNoErr(t, err)
+}
diff --git a/rackspace/db/v1/users/results.go b/rackspace/db/v1/users/results.go
new file mode 100644
index 0000000..3537e68
--- /dev/null
+++ b/rackspace/db/v1/users/results.go
@@ -0,0 +1,97 @@
+package users
+
+import (
+	"github.com/mitchellh/mapstructure"
+	"github.com/rackspace/gophercloud"
+	db "github.com/rackspace/gophercloud/openstack/db/v1/databases"
+	"github.com/rackspace/gophercloud/pagination"
+)
+
+// User represents a database user
+type User struct {
+	// The user name
+	Name string
+
+	// The user password
+	Password string
+
+	Host string
+
+	// The databases associated with this user
+	Databases []db.Database
+}
+
+type UpdatePasswordsResult struct {
+	gophercloud.ErrResult
+}
+
+type UpdateResult struct {
+	gophercloud.ErrResult
+}
+
+type GetResult struct {
+	gophercloud.Result
+}
+
+func (r GetResult) Extract() (*User, error) {
+	if r.Err != nil {
+		return nil, r.Err
+	}
+
+	var response struct {
+		User User `mapstructure:"user"`
+	}
+
+	err := mapstructure.Decode(r.Body, &response)
+	return &response.User, err
+}
+
+// AccessPage represents a single page of a paginated user collection.
+type AccessPage struct {
+	pagination.LinkedPageBase
+}
+
+// IsEmpty checks to see whether the collection is empty.
+func (page AccessPage) IsEmpty() (bool, error) {
+	users, err := ExtractDBs(page)
+	if err != nil {
+		return true, err
+	}
+	return len(users) == 0, nil
+}
+
+// NextPageURL will retrieve the next page URL.
+func (page AccessPage) NextPageURL() (string, error) {
+	type resp struct {
+		Links []gophercloud.Link `mapstructure:"databases_links"`
+	}
+
+	var r resp
+	err := mapstructure.Decode(page.Body, &r)
+	if err != nil {
+		return "", err
+	}
+
+	return gophercloud.ExtractNextURL(r.Links)
+}
+
+// ExtractDBs will convert a generic pagination struct into a more
+// relevant slice of DB structs.
+func ExtractDBs(page pagination.Page) ([]db.Database, error) {
+	casted := page.(AccessPage).Body
+
+	var response struct {
+		DBs []db.Database `mapstructure:"databases"`
+	}
+
+	err := mapstructure.Decode(casted, &response)
+	return response.DBs, err
+}
+
+type GrantAccessResult struct {
+	gophercloud.ErrResult
+}
+
+type RevokeAccessResult struct {
+	gophercloud.ErrResult
+}
diff --git a/rackspace/db/v1/users/urls.go b/rackspace/db/v1/users/urls.go
new file mode 100644
index 0000000..bac8788
--- /dev/null
+++ b/rackspace/db/v1/users/urls.go
@@ -0,0 +1,19 @@
+package users
+
+import "github.com/rackspace/gophercloud"
+
+func baseURL(c *gophercloud.ServiceClient, instanceID string) string {
+	return c.ServiceURL("instances", instanceID, "users")
+}
+
+func userURL(c *gophercloud.ServiceClient, instanceID, userName string) string {
+	return c.ServiceURL("instances", instanceID, "users", userName)
+}
+
+func dbsURL(c *gophercloud.ServiceClient, instanceID, userName string) string {
+	return c.ServiceURL("instances", instanceID, "users", userName, "databases")
+}
+
+func dbURL(c *gophercloud.ServiceClient, instanceID, userName, dbName string) string {
+	return c.ServiceURL("instances", instanceID, "users", userName, "databases", dbName)
+}