Keystone Identity /v3/role_assignments
diff --git a/openstack/identity/v3/roles/doc.go b/openstack/identity/v3/roles/doc.go
new file mode 100644
index 0000000..bdbc674
--- /dev/null
+++ b/openstack/identity/v3/roles/doc.go
@@ -0,0 +1,3 @@
+// Package roles provides information and interaction with the roles API
+// resource for the OpenStack Identity service.
+package roles
diff --git a/openstack/identity/v3/roles/requests.go b/openstack/identity/v3/roles/requests.go
new file mode 100644
index 0000000..cfa1b38
--- /dev/null
+++ b/openstack/identity/v3/roles/requests.go
@@ -0,0 +1,31 @@
+package roles
+
+import (
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/pagination"
+)
+
+// RoleAssignmentsOpts allows you to query the RoleAssignments method.
+type RoleAssignmentsOpts struct {
+	GroupId        string `q:"group.id"`
+	RoleId         string `q:"role.id"`
+	ScopeDomainId  string `q:"scope.domain.id"`
+	ScopeProjectId string `q:"scope.project.id"`
+	UserId         string `q:"user.id"`
+	Effective      bool   `q:"effective"`
+}
+
+// RoleAssignments enumerates the roles assigned to a specified resource.
+func RoleAssignments(client *gophercloud.ServiceClient, opts RoleAssignmentsOpts) pagination.Pager {
+	u := roleAssignmentsURL(client)
+	q, err := gophercloud.BuildQueryString(opts)
+	if err != nil {
+		return pagination.Pager{Err: err}
+	}
+	u += q.String()
+	createPage := func(r pagination.PageResult) pagination.Page {
+		return RoleAssignmentsPage{pagination.LinkedPageBase{PageResult: r}}
+	}
+
+	return pagination.NewPager(client, u, createPage)
+}
diff --git a/openstack/identity/v3/roles/requests_test.go b/openstack/identity/v3/roles/requests_test.go
new file mode 100644
index 0000000..3ce88b4
--- /dev/null
+++ b/openstack/identity/v3/roles/requests_test.go
@@ -0,0 +1,104 @@
+package roles
+
+import (
+	"fmt"
+	"net/http"
+	"reflect"
+	"testing"
+
+	"github.com/rackspace/gophercloud/pagination"
+	"github.com/rackspace/gophercloud/testhelper"
+	"github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func TestListSinglePage(t *testing.T) {
+	testhelper.SetupHTTP()
+	defer testhelper.TeardownHTTP()
+
+	testhelper.Mux.HandleFunc("/role_assignments", func(w http.ResponseWriter, r *http.Request) {
+		testhelper.TestMethod(t, r, "GET")
+		testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+
+		w.Header().Add("Content-Type", "application/json")
+		fmt.Fprintf(w, `
+			{
+                "role_assignments": [
+                    {
+                        "links": {
+                            "assignment": "http://identity:35357/v3/domains/161718/users/313233/roles/123456"
+                        },
+                        "role": {
+                            "id": "123456"
+                        },
+                        "scope": {
+                            "domain": {
+                                "id": "161718"
+                            }
+                        },
+                        "user": {
+                            "id": "313233"
+                        }
+                    },
+                    {
+                        "links": {
+                            "assignment": "http://identity:35357/v3/projects/456789/groups/101112/roles/123456",
+                            "membership": "http://identity:35357/v3/groups/101112/users/313233"
+                        },
+                        "role": {
+                            "id": "123456"
+                        },
+                        "scope": {
+                            "project": {
+                                "id": "456789"
+                            }
+                        },
+                        "user": {
+                            "id": "313233"
+                        }
+                    }
+                ],
+                "links": {
+                    "self": "http://identity:35357/v3/role_assignments?effective",
+                    "previous": null,
+                    "next": null
+                }
+            }
+		`)
+	})
+
+	count := 0
+	err := RoleAssignments(client.ServiceClient(), RoleAssignmentsOpts{}).EachPage(func(page pagination.Page) (bool, error) {
+		count++
+		actual, err := ExtractRoleAssignments(page)
+		if err != nil {
+			return false, err
+		}
+
+		expected := []RoleAssignment{
+			RoleAssignment{
+				Role:  &Role{ID: "123456"},
+				Scope: &Scope{Domain: &Domain{ID: "161718"}},
+				User:  &User{ID: "313233"},
+				Group: nil,
+			},
+			RoleAssignment{
+				Role:  &Role{ID: "123456"},
+				Scope: &Scope{Project: &Project{ID: "456789"}},
+				User:  &User{ID: "313233"},
+				Group: nil,
+			},
+		}
+
+		if !reflect.DeepEqual(expected, actual) {
+			t.Errorf("Expected %#v, got %#v", expected, actual)
+		}
+
+		return true, nil
+	})
+	if err != nil {
+		t.Errorf("Unexpected error while paging: %v", err)
+	}
+	if count != 1 {
+		t.Errorf("Expected 1 page, got %d", count)
+	}
+}
diff --git a/openstack/identity/v3/roles/results.go b/openstack/identity/v3/roles/results.go
new file mode 100644
index 0000000..31cb7a3
--- /dev/null
+++ b/openstack/identity/v3/roles/results.go
@@ -0,0 +1,64 @@
+package roles
+
+import (
+	"github.com/rackspace/gophercloud/pagination"
+
+	"github.com/mitchellh/mapstructure"
+)
+
+// RoleAssignment is the result of a role assignments query.
+type RoleAssignment struct {
+	Role  *Role  `json:"role,omitempty"`
+	Scope *Scope `json:"scope,omitempty"`
+	User  *User  `json:"user,omitempty"`
+	Group *Group `json:"group,omitempty"`
+}
+
+type Role struct {
+	ID string `json:"id,omitempty"`
+}
+
+type Scope struct {
+	Domain  *Domain  `json:"domain,omitempty"`
+	Project *Project `json:"domain,omitempty"`
+}
+
+type Domain struct {
+	ID string `json:"id,omitempty"`
+}
+
+type Project struct {
+	ID string `json:"id,omitempty"`
+}
+
+type User struct {
+	ID string `json:"id,omitempty"`
+}
+
+type Group struct {
+	ID string `json:"id,omitempty"`
+}
+
+// RoleAssignmentsPage is a single page of RoleAssignments results.
+type RoleAssignmentsPage struct {
+	pagination.LinkedPageBase
+}
+
+// IsEmpty returns true if the page contains no results.
+func (p RoleAssignmentsPage) IsEmpty() (bool, error) {
+	roleAssignments, err := ExtractRoleAssignments(p)
+	if err != nil {
+		return true, err
+	}
+	return len(roleAssignments) == 0, nil
+}
+
+// ExtractRoleAssignments extracts a slice of RoleAssignments from a Collection acquired from List.
+func ExtractRoleAssignments(page pagination.Page) ([]RoleAssignment, error) {
+	var response struct {
+		RoleAssignments []RoleAssignment `mapstructure:"role_assignments"`
+	}
+
+	err := mapstructure.Decode(page.(RoleAssignmentsPage).Body, &response)
+	return response.RoleAssignments, err
+}
diff --git a/openstack/identity/v3/roles/urls.go b/openstack/identity/v3/roles/urls.go
new file mode 100644
index 0000000..8068ac3
--- /dev/null
+++ b/openstack/identity/v3/roles/urls.go
@@ -0,0 +1,7 @@
+package roles
+
+import "github.com/rackspace/gophercloud"
+
+func roleAssignmentsURL(client *gophercloud.ServiceClient) string {
+	return client.ServiceURL("role_assignments")
+}
diff --git a/openstack/identity/v3/roles/urls_test.go b/openstack/identity/v3/roles/urls_test.go
new file mode 100644
index 0000000..8e70e22
--- /dev/null
+++ b/openstack/identity/v3/roles/urls_test.go
@@ -0,0 +1,15 @@
+package roles
+
+import (
+	"testing"
+
+	"github.com/rackspace/gophercloud"
+)
+
+func TestRoleAssignmentsURL(t *testing.T) {
+	client := gophercloud.ServiceClient{Endpoint: "http://localhost:5000/v3/"}
+	url := roleAssignmentsURL(&client)
+	if url != "http://localhost:5000/v3/role_assignments" {
+		t.Errorf("Unexpected list URL generated: [%s]", url)
+	}
+}