Merge pull request #357 from jrperritt/orchestration

OpenStack/Rackspace Orchestration
diff --git a/acceptance/openstack/orchestration/v1/buildinfo_test.go b/acceptance/openstack/orchestration/v1/buildinfo_test.go
new file mode 100644
index 0000000..05a5e1d
--- /dev/null
+++ b/acceptance/openstack/orchestration/v1/buildinfo_test.go
@@ -0,0 +1,20 @@
+// +build acceptance
+
+package v1
+
+import (
+	"testing"
+
+	"github.com/rackspace/gophercloud/openstack/orchestration/v1/buildinfo"
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestBuildInfo(t *testing.T) {
+	// Create a provider client for making the HTTP requests.
+	// See common.go in this directory for more information.
+	client := newClient(t)
+
+	bi, err := buildinfo.Get(client).Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("retrieved build info: %+v\n", bi)
+}
diff --git a/acceptance/openstack/orchestration/v1/common.go b/acceptance/openstack/orchestration/v1/common.go
new file mode 100644
index 0000000..2c28dcb
--- /dev/null
+++ b/acceptance/openstack/orchestration/v1/common.go
@@ -0,0 +1,44 @@
+// +build acceptance
+
+package v1
+
+import (
+	"fmt"
+	"os"
+	"testing"
+
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/openstack"
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+var template = fmt.Sprintf(`
+{
+	"heat_template_version": "2013-05-23",
+	"description": "Simple template to test heat commands",
+	"parameters": {},
+	"resources": {
+		"hello_world": {
+			"type":"OS::Nova::Server",
+			"properties": {
+				"flavor": "%s",
+				"image": "%s",
+				"user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n"
+			}
+		}
+	}
+}`, os.Getenv("OS_FLAVOR_ID"), os.Getenv("OS_IMAGE_ID"))
+
+func newClient(t *testing.T) *gophercloud.ServiceClient {
+	ao, err := openstack.AuthOptionsFromEnv()
+	th.AssertNoErr(t, err)
+
+	client, err := openstack.AuthenticatedClient(ao)
+	th.AssertNoErr(t, err)
+
+	c, err := openstack.NewOrchestrationV1(client, gophercloud.EndpointOpts{
+		Region: os.Getenv("OS_REGION_NAME"),
+	})
+	th.AssertNoErr(t, err)
+	return c
+}
diff --git a/acceptance/openstack/orchestration/v1/hello-compute.json b/acceptance/openstack/orchestration/v1/hello-compute.json
new file mode 100644
index 0000000..11cfc80
--- /dev/null
+++ b/acceptance/openstack/orchestration/v1/hello-compute.json
@@ -0,0 +1,13 @@
+{
+  "heat_template_version": "2013-05-23",
+  "resources": {
+    "compute_instance": {
+      "type": "OS::Nova::Server",
+      "properties": {
+        "flavor": "m1.small",
+        "image": "cirros-0.3.2-x86_64-disk",
+        "name": "Single Compute Instance"
+      }
+    }
+  }
+}
diff --git a/acceptance/openstack/orchestration/v1/stackevents_test.go b/acceptance/openstack/orchestration/v1/stackevents_test.go
new file mode 100644
index 0000000..e356c86
--- /dev/null
+++ b/acceptance/openstack/orchestration/v1/stackevents_test.go
@@ -0,0 +1,68 @@
+// +build acceptance
+
+package v1
+
+import (
+	"testing"
+
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/openstack/orchestration/v1/stackevents"
+	"github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks"
+	"github.com/rackspace/gophercloud/pagination"
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestStackEvents(t *testing.T) {
+	// Create a provider client for making the HTTP requests.
+	// See common.go in this directory for more information.
+	client := newClient(t)
+
+	stackName := "postman_stack_2"
+	resourceName := "hello_world"
+	var eventID string
+
+	createOpts := stacks.CreateOpts{
+		Name:     stackName,
+		Template: template,
+		Timeout:  5,
+	}
+	stack, err := stacks.Create(client, createOpts).Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("Created stack: %+v\n", stack)
+	defer func() {
+		err := stacks.Delete(client, stackName, stack.ID).ExtractErr()
+		th.AssertNoErr(t, err)
+		t.Logf("Deleted stack (%s)", stackName)
+	}()
+	err = gophercloud.WaitFor(60, func() (bool, error) {
+		getStack, err := stacks.Get(client, stackName, stack.ID).Extract()
+		if err != nil {
+			return false, err
+		}
+		if getStack.Status == "CREATE_COMPLETE" {
+			return true, nil
+		}
+		return false, nil
+	})
+
+	err = stackevents.List(client, stackName, stack.ID, nil).EachPage(func(page pagination.Page) (bool, error) {
+		events, err := stackevents.ExtractEvents(page)
+		th.AssertNoErr(t, err)
+		t.Logf("listed events: %+v\n", events)
+		eventID = events[0].ID
+		return false, nil
+	})
+	th.AssertNoErr(t, err)
+
+	err = stackevents.ListResourceEvents(client, stackName, stack.ID, resourceName, nil).EachPage(func(page pagination.Page) (bool, error) {
+		resourceEvents, err := stackevents.ExtractEvents(page)
+		th.AssertNoErr(t, err)
+		t.Logf("listed resource events: %+v\n", resourceEvents)
+		return false, nil
+	})
+	th.AssertNoErr(t, err)
+
+	event, err := stackevents.Get(client, stackName, stack.ID, resourceName, eventID).Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("retrieved event: %+v\n", event)
+}
diff --git a/acceptance/openstack/orchestration/v1/stackresources_test.go b/acceptance/openstack/orchestration/v1/stackresources_test.go
new file mode 100644
index 0000000..b614f1c
--- /dev/null
+++ b/acceptance/openstack/orchestration/v1/stackresources_test.go
@@ -0,0 +1,62 @@
+// +build acceptance
+
+package v1
+
+import (
+	"testing"
+
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources"
+	"github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks"
+	"github.com/rackspace/gophercloud/pagination"
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestStackResources(t *testing.T) {
+	// Create a provider client for making the HTTP requests.
+	// See common.go in this directory for more information.
+	client := newClient(t)
+
+	stackName := "postman_stack_2"
+
+	createOpts := stacks.CreateOpts{
+		Name:     stackName,
+		Template: template,
+		Timeout:  5,
+	}
+	stack, err := stacks.Create(client, createOpts).Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("Created stack: %+v\n", stack)
+	defer func() {
+		err := stacks.Delete(client, stackName, stack.ID).ExtractErr()
+		th.AssertNoErr(t, err)
+		t.Logf("Deleted stack (%s)", stackName)
+	}()
+	err = gophercloud.WaitFor(60, func() (bool, error) {
+		getStack, err := stacks.Get(client, stackName, stack.ID).Extract()
+		if err != nil {
+			return false, err
+		}
+		if getStack.Status == "CREATE_COMPLETE" {
+			return true, nil
+		}
+		return false, nil
+	})
+
+	resourceName := "hello_world"
+	resource, err := stackresources.Get(client, stackName, stack.ID, resourceName).Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("Got stack resource: %+v\n", resource)
+
+	metadata, err := stackresources.Metadata(client, stackName, stack.ID, resourceName).Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("Got stack resource metadata: %+v\n", metadata)
+
+	err = stackresources.List(client, stackName, stack.ID, stackresources.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
+		resources, err := stackresources.ExtractResources(page)
+		th.AssertNoErr(t, err)
+		t.Logf("resources: %+v\n", resources)
+		return false, nil
+	})
+	th.AssertNoErr(t, err)
+}
diff --git a/acceptance/openstack/orchestration/v1/stacks_test.go b/acceptance/openstack/orchestration/v1/stacks_test.go
new file mode 100644
index 0000000..01e76d6
--- /dev/null
+++ b/acceptance/openstack/orchestration/v1/stacks_test.go
@@ -0,0 +1,81 @@
+// +build acceptance
+
+package v1
+
+import (
+	"testing"
+
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks"
+	"github.com/rackspace/gophercloud/pagination"
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestStacks(t *testing.T) {
+	// Create a provider client for making the HTTP requests.
+	// See common.go in this directory for more information.
+	client := newClient(t)
+
+	stackName1 := "gophercloud-test-stack-2"
+	createOpts := stacks.CreateOpts{
+		Name:     stackName1,
+		Template: template,
+		Timeout:  5,
+	}
+	stack, err := stacks.Create(client, createOpts).Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("Created stack: %+v\n", stack)
+	defer func() {
+		err := stacks.Delete(client, stackName1, stack.ID).ExtractErr()
+		th.AssertNoErr(t, err)
+		t.Logf("Deleted stack (%s)", stackName1)
+	}()
+	err = gophercloud.WaitFor(60, func() (bool, error) {
+		getStack, err := stacks.Get(client, stackName1, stack.ID).Extract()
+		if err != nil {
+			return false, err
+		}
+		if getStack.Status == "CREATE_COMPLETE" {
+			return true, nil
+		}
+		return false, nil
+	})
+
+	updateOpts := stacks.UpdateOpts{
+		Template: template,
+		Timeout:  20,
+	}
+	err = stacks.Update(client, stackName1, stack.ID, updateOpts).ExtractErr()
+	th.AssertNoErr(t, err)
+	err = gophercloud.WaitFor(60, func() (bool, error) {
+		getStack, err := stacks.Get(client, stackName1, stack.ID).Extract()
+		if err != nil {
+			return false, err
+		}
+		if getStack.Status == "UPDATE_COMPLETE" {
+			return true, nil
+		}
+		return false, nil
+	})
+
+	t.Logf("Updated stack")
+
+	err = stacks.List(client, nil).EachPage(func(page pagination.Page) (bool, error) {
+		stackList, err := stacks.ExtractStacks(page)
+		th.AssertNoErr(t, err)
+
+		t.Logf("Got stack list: %+v\n", stackList)
+
+		return true, nil
+	})
+	th.AssertNoErr(t, err)
+
+	getStack, err := stacks.Get(client, stackName1, stack.ID).Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("Got stack: %+v\n", getStack)
+
+	abandonedStack, err := stacks.Abandon(client, stackName1, stack.ID).Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("Abandonded stack %+v\n", abandonedStack)
+	th.AssertNoErr(t, err)
+}
diff --git a/acceptance/openstack/orchestration/v1/stacktemplates_test.go b/acceptance/openstack/orchestration/v1/stacktemplates_test.go
new file mode 100644
index 0000000..14d8f44
--- /dev/null
+++ b/acceptance/openstack/orchestration/v1/stacktemplates_test.go
@@ -0,0 +1,77 @@
+// +build acceptance
+
+package v1
+
+import (
+	"testing"
+
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks"
+	"github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates"
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestStackTemplates(t *testing.T) {
+	// Create a provider client for making the HTTP requests.
+	// See common.go in this directory for more information.
+	client := newClient(t)
+
+	stackName := "postman_stack_2"
+
+	createOpts := stacks.CreateOpts{
+		Name:     stackName,
+		Template: template,
+		Timeout:  5,
+	}
+	stack, err := stacks.Create(client, createOpts).Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("Created stack: %+v\n", stack)
+	defer func() {
+		err := stacks.Delete(client, stackName, stack.ID).ExtractErr()
+		th.AssertNoErr(t, err)
+		t.Logf("Deleted stack (%s)", stackName)
+	}()
+	err = gophercloud.WaitFor(60, func() (bool, error) {
+		getStack, err := stacks.Get(client, stackName, stack.ID).Extract()
+		if err != nil {
+			return false, err
+		}
+		if getStack.Status == "CREATE_COMPLETE" {
+			return true, nil
+		}
+		return false, nil
+	})
+
+	tmpl, err := stacktemplates.Get(client, stackName, stack.ID).Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("retrieved template: %+v\n", tmpl)
+
+	validateOpts := stacktemplates.ValidateOpts{
+		Template: map[string]interface{}{
+			"heat_template_version": "2013-05-23",
+			"description":           "Simple template to test heat commands",
+			"parameters": map[string]interface{}{
+				"flavor": map[string]interface{}{
+					"default": "m1.tiny",
+					"type":    "string",
+				},
+			},
+			"resources": map[string]interface{}{
+				"hello_world": map[string]interface{}{
+					"type": "OS::Nova::Server",
+					"properties": map[string]interface{}{
+						"key_name": "heat_key",
+						"flavor": map[string]interface{}{
+							"get_param": "flavor",
+						},
+						"image":     "ad091b52-742f-469e-8f3c-fd81cadf0743",
+						"user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n",
+					},
+				},
+			},
+		},
+	}
+	validatedTemplate, err := stacktemplates.Validate(client, validateOpts).Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("validated template: %+v\n", validatedTemplate)
+}
diff --git a/acceptance/rackspace/orchestration/v1/buildinfo_test.go b/acceptance/rackspace/orchestration/v1/buildinfo_test.go
new file mode 100644
index 0000000..42cc048
--- /dev/null
+++ b/acceptance/rackspace/orchestration/v1/buildinfo_test.go
@@ -0,0 +1,20 @@
+// +build acceptance
+
+package v1
+
+import (
+	"testing"
+
+	"github.com/rackspace/gophercloud/rackspace/orchestration/v1/buildinfo"
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestBuildInfo(t *testing.T) {
+	// Create a provider client for making the HTTP requests.
+	// See common.go in this directory for more information.
+	client := newClient(t)
+
+	bi, err := buildinfo.Get(client).Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("retrieved build info: %+v\n", bi)
+}
diff --git a/acceptance/rackspace/orchestration/v1/common.go b/acceptance/rackspace/orchestration/v1/common.go
new file mode 100644
index 0000000..b9d5197
--- /dev/null
+++ b/acceptance/rackspace/orchestration/v1/common.go
@@ -0,0 +1,45 @@
+// +build acceptance
+
+package v1
+
+import (
+	"fmt"
+	"os"
+	"testing"
+
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/rackspace"
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+var template = fmt.Sprintf(`
+{
+		"heat_template_version": "2013-05-23",
+		"description": "Simple template to test heat commands",
+		"parameters": {},
+		"resources": {
+				"hello_world": {
+						"type":"OS::Nova::Server",
+						"properties": {
+								"flavor": "%s",
+								"image": "%s",
+								"user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n"
+						}
+				}
+		}
+}
+`, os.Getenv("RS_FLAVOR_ID"), os.Getenv("RS_IMAGE_ID"))
+
+func newClient(t *testing.T) *gophercloud.ServiceClient {
+	ao, err := rackspace.AuthOptionsFromEnv()
+	th.AssertNoErr(t, err)
+
+	client, err := rackspace.AuthenticatedClient(ao)
+	th.AssertNoErr(t, err)
+
+	c, err := rackspace.NewOrchestrationV1(client, gophercloud.EndpointOpts{
+		Region: os.Getenv("RS_REGION_NAME"),
+	})
+	th.AssertNoErr(t, err)
+	return c
+}
diff --git a/acceptance/rackspace/orchestration/v1/stackevents_test.go b/acceptance/rackspace/orchestration/v1/stackevents_test.go
new file mode 100644
index 0000000..9e3fc08
--- /dev/null
+++ b/acceptance/rackspace/orchestration/v1/stackevents_test.go
@@ -0,0 +1,70 @@
+// +build acceptance
+
+package v1
+
+import (
+	"testing"
+
+	"github.com/rackspace/gophercloud"
+	osStackEvents "github.com/rackspace/gophercloud/openstack/orchestration/v1/stackevents"
+	osStacks "github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks"
+	"github.com/rackspace/gophercloud/pagination"
+	"github.com/rackspace/gophercloud/rackspace/orchestration/v1/stackevents"
+	"github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacks"
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestStackEvents(t *testing.T) {
+	// Create a provider client for making the HTTP requests.
+	// See common.go in this directory for more information.
+	client := newClient(t)
+
+	stackName := "postman_stack_2"
+	resourceName := "hello_world"
+	var eventID string
+
+	createOpts := osStacks.CreateOpts{
+		Name:     stackName,
+		Template: template,
+		Timeout:  5,
+	}
+	stack, err := stacks.Create(client, createOpts).Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("Created stack: %+v\n", stack)
+	defer func() {
+		err := stacks.Delete(client, stackName, stack.ID).ExtractErr()
+		th.AssertNoErr(t, err)
+		t.Logf("Deleted stack (%s)", stackName)
+	}()
+	err = gophercloud.WaitFor(60, func() (bool, error) {
+		getStack, err := stacks.Get(client, stackName, stack.ID).Extract()
+		if err != nil {
+			return false, err
+		}
+		if getStack.Status == "CREATE_COMPLETE" {
+			return true, nil
+		}
+		return false, nil
+	})
+
+	err = stackevents.List(client, stackName, stack.ID, nil).EachPage(func(page pagination.Page) (bool, error) {
+		events, err := osStackEvents.ExtractEvents(page)
+		th.AssertNoErr(t, err)
+		t.Logf("listed events: %+v\n", events)
+		eventID = events[0].ID
+		return false, nil
+	})
+	th.AssertNoErr(t, err)
+
+	err = stackevents.ListResourceEvents(client, stackName, stack.ID, resourceName, nil).EachPage(func(page pagination.Page) (bool, error) {
+		resourceEvents, err := osStackEvents.ExtractResourceEvents(page)
+		th.AssertNoErr(t, err)
+		t.Logf("listed resource events: %+v\n", resourceEvents)
+		return false, nil
+	})
+	th.AssertNoErr(t, err)
+
+	event, err := stackevents.Get(client, stackName, stack.ID, resourceName, eventID).Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("retrieved event: %+v\n", event)
+}
diff --git a/acceptance/rackspace/orchestration/v1/stackresources_test.go b/acceptance/rackspace/orchestration/v1/stackresources_test.go
new file mode 100644
index 0000000..65926e7
--- /dev/null
+++ b/acceptance/rackspace/orchestration/v1/stackresources_test.go
@@ -0,0 +1,64 @@
+// +build acceptance
+
+package v1
+
+import (
+	"testing"
+
+	"github.com/rackspace/gophercloud"
+	osStackResources "github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources"
+	osStacks "github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks"
+	"github.com/rackspace/gophercloud/pagination"
+	"github.com/rackspace/gophercloud/rackspace/orchestration/v1/stackresources"
+	"github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacks"
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestStackResources(t *testing.T) {
+	// Create a provider client for making the HTTP requests.
+	// See common.go in this directory for more information.
+	client := newClient(t)
+
+	stackName := "postman_stack_2"
+
+	createOpts := osStacks.CreateOpts{
+		Name:     stackName,
+		Template: template,
+		Timeout:  5,
+	}
+	stack, err := stacks.Create(client, createOpts).Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("Created stack: %+v\n", stack)
+	defer func() {
+		err := stacks.Delete(client, stackName, stack.ID).ExtractErr()
+		th.AssertNoErr(t, err)
+		t.Logf("Deleted stack (%s)", stackName)
+	}()
+	err = gophercloud.WaitFor(60, func() (bool, error) {
+		getStack, err := stacks.Get(client, stackName, stack.ID).Extract()
+		if err != nil {
+			return false, err
+		}
+		if getStack.Status == "CREATE_COMPLETE" {
+			return true, nil
+		}
+		return false, nil
+	})
+
+	resourceName := "hello_world"
+	resource, err := stackresources.Get(client, stackName, stack.ID, resourceName).Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("Got stack resource: %+v\n", resource)
+
+	metadata, err := stackresources.Metadata(client, stackName, stack.ID, resourceName).Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("Got stack resource metadata: %+v\n", metadata)
+
+	err = stackresources.List(client, stackName, stack.ID, nil).EachPage(func(page pagination.Page) (bool, error) {
+		resources, err := osStackResources.ExtractResources(page)
+		th.AssertNoErr(t, err)
+		t.Logf("resources: %+v\n", resources)
+		return false, nil
+	})
+	th.AssertNoErr(t, err)
+}
diff --git a/acceptance/rackspace/orchestration/v1/stacks_test.go b/acceptance/rackspace/orchestration/v1/stacks_test.go
new file mode 100644
index 0000000..cfec4e9
--- /dev/null
+++ b/acceptance/rackspace/orchestration/v1/stacks_test.go
@@ -0,0 +1,82 @@
+// +build acceptance
+
+package v1
+
+import (
+	"testing"
+
+	"github.com/rackspace/gophercloud"
+	osStacks "github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks"
+	"github.com/rackspace/gophercloud/pagination"
+	"github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacks"
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestStacks(t *testing.T) {
+	// Create a provider client for making the HTTP requests.
+	// See common.go in this directory for more information.
+	client := newClient(t)
+
+	stackName1 := "gophercloud-test-stack-2"
+	createOpts := osStacks.CreateOpts{
+		Name:     stackName1,
+		Template: template,
+		Timeout:  5,
+	}
+	stack, err := stacks.Create(client, createOpts).Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("Created stack: %+v\n", stack)
+	defer func() {
+		err := stacks.Delete(client, stackName1, stack.ID).ExtractErr()
+		th.AssertNoErr(t, err)
+		t.Logf("Deleted stack (%s)", stackName1)
+	}()
+	err = gophercloud.WaitFor(60, func() (bool, error) {
+		getStack, err := stacks.Get(client, stackName1, stack.ID).Extract()
+		if err != nil {
+			return false, err
+		}
+		if getStack.Status == "CREATE_COMPLETE" {
+			return true, nil
+		}
+		return false, nil
+	})
+
+	updateOpts := osStacks.UpdateOpts{
+		Template: template,
+		Timeout:  20,
+	}
+	err = stacks.Update(client, stackName1, stack.ID, updateOpts).ExtractErr()
+	th.AssertNoErr(t, err)
+	err = gophercloud.WaitFor(60, func() (bool, error) {
+		getStack, err := stacks.Get(client, stackName1, stack.ID).Extract()
+		if err != nil {
+			return false, err
+		}
+		if getStack.Status == "UPDATE_COMPLETE" {
+			return true, nil
+		}
+		return false, nil
+	})
+
+	t.Logf("Updated stack")
+
+	err = stacks.List(client, nil).EachPage(func(page pagination.Page) (bool, error) {
+		stackList, err := osStacks.ExtractStacks(page)
+		th.AssertNoErr(t, err)
+
+		t.Logf("Got stack list: %+v\n", stackList)
+
+		return true, nil
+	})
+	th.AssertNoErr(t, err)
+
+	getStack, err := stacks.Get(client, stackName1, stack.ID).Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("Got stack: %+v\n", getStack)
+
+	abandonedStack, err := stacks.Abandon(client, stackName1, stack.ID).Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("Abandonded stack %+v\n", abandonedStack)
+	th.AssertNoErr(t, err)
+}
diff --git a/acceptance/rackspace/orchestration/v1/stacktemplates_test.go b/acceptance/rackspace/orchestration/v1/stacktemplates_test.go
new file mode 100644
index 0000000..1f7b217
--- /dev/null
+++ b/acceptance/rackspace/orchestration/v1/stacktemplates_test.go
@@ -0,0 +1,79 @@
+// +build acceptance
+
+package v1
+
+import (
+	"testing"
+
+	"github.com/rackspace/gophercloud"
+	osStacks "github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks"
+	osStacktemplates "github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates"
+	"github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacks"
+	"github.com/rackspace/gophercloud/rackspace/orchestration/v1/stacktemplates"
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestStackTemplates(t *testing.T) {
+	// Create a provider client for making the HTTP requests.
+	// See common.go in this directory for more information.
+	client := newClient(t)
+
+	stackName := "postman_stack_2"
+
+	createOpts := osStacks.CreateOpts{
+		Name:     stackName,
+		Template: template,
+		Timeout:  5,
+	}
+	stack, err := stacks.Create(client, createOpts).Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("Created stack: %+v\n", stack)
+	defer func() {
+		err := stacks.Delete(client, stackName, stack.ID).ExtractErr()
+		th.AssertNoErr(t, err)
+		t.Logf("Deleted stack (%s)", stackName)
+	}()
+	err = gophercloud.WaitFor(60, func() (bool, error) {
+		getStack, err := stacks.Get(client, stackName, stack.ID).Extract()
+		if err != nil {
+			return false, err
+		}
+		if getStack.Status == "CREATE_COMPLETE" {
+			return true, nil
+		}
+		return false, nil
+	})
+
+	tmpl, err := stacktemplates.Get(client, stackName, stack.ID).Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("retrieved template: %+v\n", tmpl)
+
+	validateOpts := osStacktemplates.ValidateOpts{
+		Template: map[string]interface{}{
+			"heat_template_version": "2013-05-23",
+			"description":           "Simple template to test heat commands",
+			"parameters": map[string]interface{}{
+				"flavor": map[string]interface{}{
+					"default": "m1.tiny",
+					"type":    "string",
+				},
+			},
+			"resources": map[string]interface{}{
+				"hello_world": map[string]interface{}{
+					"type": "OS::Nova::Server",
+					"properties": map[string]interface{}{
+						"key_name": "heat_key",
+						"flavor": map[string]interface{}{
+							"get_param": "flavor",
+						},
+						"image":     "ad091b52-742f-469e-8f3c-fd81cadf0743",
+						"user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n",
+					},
+				},
+			},
+		},
+	}
+	validatedTemplate, err := stacktemplates.Validate(client, validateOpts).Extract()
+	th.AssertNoErr(t, err)
+	t.Logf("validated template: %+v\n", validatedTemplate)
+}
diff --git a/openstack/client.go b/openstack/client.go
index 9c12dca..5fce3d6 100644
--- a/openstack/client.go
+++ b/openstack/client.go
@@ -214,3 +214,13 @@
 	}
 	return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
 }
+
+// NewOrchestrationV1 creates a ServiceClient that may be used to access the v1 orchestration service.
+func NewOrchestrationV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
+	eo.ApplyDefaults("orchestration")
+	url, err := client.EndpointLocator(eo)
+	if err != nil {
+		return nil, err
+	}
+	return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
+}
diff --git a/openstack/orchestration/v1/apiversions/doc.go b/openstack/orchestration/v1/apiversions/doc.go
new file mode 100644
index 0000000..f2db622
--- /dev/null
+++ b/openstack/orchestration/v1/apiversions/doc.go
@@ -0,0 +1,4 @@
+// Package apiversions provides information and interaction with the different
+// API versions for the OpenStack Heat service. This functionality is not
+// restricted to this particular version.
+package apiversions
diff --git a/openstack/orchestration/v1/apiversions/requests.go b/openstack/orchestration/v1/apiversions/requests.go
new file mode 100644
index 0000000..f6454c8
--- /dev/null
+++ b/openstack/orchestration/v1/apiversions/requests.go
@@ -0,0 +1,13 @@
+package apiversions
+
+import (
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/pagination"
+)
+
+// ListVersions lists all the Neutron API versions available to end-users
+func ListVersions(c *gophercloud.ServiceClient) pagination.Pager {
+	return pagination.NewPager(c, apiVersionsURL(c), func(r pagination.PageResult) pagination.Page {
+		return APIVersionPage{pagination.SinglePageBase(r)}
+	})
+}
diff --git a/openstack/orchestration/v1/apiversions/requests_test.go b/openstack/orchestration/v1/apiversions/requests_test.go
new file mode 100644
index 0000000..a2fc980
--- /dev/null
+++ b/openstack/orchestration/v1/apiversions/requests_test.go
@@ -0,0 +1,89 @@
+package apiversions
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/pagination"
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func TestListVersions(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	th.Mux.HandleFunc("/", 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")
+		w.WriteHeader(http.StatusOK)
+
+		fmt.Fprintf(w, `
+{
+    "versions": [
+        {
+            "status": "CURRENT",
+            "id": "v1.0",
+            "links": [
+                {
+                    "href": "http://23.253.228.211:8000/v1",
+                    "rel": "self"
+                }
+            ]
+        }
+    ]
+}`)
+	})
+
+	count := 0
+
+	ListVersions(fake.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) {
+		count++
+		actual, err := ExtractAPIVersions(page)
+		if err != nil {
+			t.Errorf("Failed to extract API versions: %v", err)
+			return false, err
+		}
+
+		expected := []APIVersion{
+			APIVersion{
+				Status: "CURRENT",
+				ID:     "v1.0",
+				Links: []gophercloud.Link{
+					gophercloud.Link{
+						Href: "http://23.253.228.211:8000/v1",
+						Rel:  "self",
+					},
+				},
+			},
+		}
+
+		th.AssertDeepEquals(t, expected, actual)
+
+		return true, nil
+	})
+
+	if count != 1 {
+		t.Errorf("Expected 1 page, got %d", count)
+	}
+}
+
+func TestNonJSONCannotBeExtractedIntoAPIVersions(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		w.WriteHeader(http.StatusOK)
+	})
+
+	ListVersions(fake.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) {
+		if _, err := ExtractAPIVersions(page); err == nil {
+			t.Fatalf("Expected error, got nil")
+		}
+		return true, nil
+	})
+}
diff --git a/openstack/orchestration/v1/apiversions/results.go b/openstack/orchestration/v1/apiversions/results.go
new file mode 100644
index 0000000..0700ab0
--- /dev/null
+++ b/openstack/orchestration/v1/apiversions/results.go
@@ -0,0 +1,42 @@
+package apiversions
+
+import (
+	"github.com/mitchellh/mapstructure"
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/pagination"
+)
+
+// APIVersion represents an API version for Neutron. It contains the status of
+// the API, and its unique ID.
+type APIVersion struct {
+	Status string             `mapstructure:"status"`
+	ID     string             `mapstructure:"id"`
+	Links  []gophercloud.Link `mapstructure:"links"`
+}
+
+// APIVersionPage is the page returned by a pager when traversing over a
+// collection of API versions.
+type APIVersionPage struct {
+	pagination.SinglePageBase
+}
+
+// IsEmpty checks whether an APIVersionPage struct is empty.
+func (r APIVersionPage) IsEmpty() (bool, error) {
+	is, err := ExtractAPIVersions(r)
+	if err != nil {
+		return true, err
+	}
+	return len(is) == 0, nil
+}
+
+// ExtractAPIVersions takes a collection page, extracts all of the elements,
+// and returns them a slice of APIVersion structs. It is effectively a cast.
+func ExtractAPIVersions(page pagination.Page) ([]APIVersion, error) {
+	var resp struct {
+		Versions []APIVersion `mapstructure:"versions"`
+	}
+
+	err := mapstructure.Decode(page.(APIVersionPage).Body, &resp)
+
+	return resp.Versions, err
+}
diff --git a/openstack/orchestration/v1/apiversions/urls.go b/openstack/orchestration/v1/apiversions/urls.go
new file mode 100644
index 0000000..55d6e0e
--- /dev/null
+++ b/openstack/orchestration/v1/apiversions/urls.go
@@ -0,0 +1,7 @@
+package apiversions
+
+import "github.com/rackspace/gophercloud"
+
+func apiVersionsURL(c *gophercloud.ServiceClient) string {
+	return c.Endpoint
+}
diff --git a/openstack/orchestration/v1/buildinfo/doc.go b/openstack/orchestration/v1/buildinfo/doc.go
new file mode 100644
index 0000000..183e8df
--- /dev/null
+++ b/openstack/orchestration/v1/buildinfo/doc.go
@@ -0,0 +1,2 @@
+// Package buildinfo provides build information about heat deployments.
+package buildinfo
diff --git a/openstack/orchestration/v1/buildinfo/fixtures.go b/openstack/orchestration/v1/buildinfo/fixtures.go
new file mode 100644
index 0000000..20ea09b
--- /dev/null
+++ b/openstack/orchestration/v1/buildinfo/fixtures.go
@@ -0,0 +1,45 @@
+package buildinfo
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+// GetExpected represents the expected object from a Get request.
+var GetExpected = &BuildInfo{
+	API: Revision{
+		Revision: "2.4.5",
+	},
+	Engine: Revision{
+		Revision: "1.2.1",
+	},
+}
+
+// GetOutput represents the response body from a Get request.
+const GetOutput = `
+{
+  "api": {
+    "revision": "2.4.5"
+  },
+  "engine": {
+    "revision": "1.2.1"
+  }
+}`
+
+// HandleGetSuccessfully creates an HTTP handler at `/build_info`
+// on the test handler mux that responds with a `Get` response.
+func HandleGetSuccessfully(t *testing.T, output string) {
+	th.Mux.HandleFunc("/build_info", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+
+		w.Header().Set("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprintf(w, output)
+	})
+}
diff --git a/openstack/orchestration/v1/buildinfo/requests.go b/openstack/orchestration/v1/buildinfo/requests.go
new file mode 100644
index 0000000..c20b48c
--- /dev/null
+++ b/openstack/orchestration/v1/buildinfo/requests.go
@@ -0,0 +1,17 @@
+package buildinfo
+
+import (
+	"github.com/racker/perigee"
+	"github.com/rackspace/gophercloud"
+)
+
+// Get retreives data for the given stack template.
+func Get(c *gophercloud.ServiceClient) GetResult {
+	var res GetResult
+	_, res.Err = perigee.Request("GET", getURL(c), perigee.Options{
+		MoreHeaders: c.AuthenticatedHeaders(),
+		Results:     &res.Body,
+		OkCodes:     []int{200},
+	})
+	return res
+}
diff --git a/openstack/orchestration/v1/buildinfo/requests_test.go b/openstack/orchestration/v1/buildinfo/requests_test.go
new file mode 100644
index 0000000..1e0fe23
--- /dev/null
+++ b/openstack/orchestration/v1/buildinfo/requests_test.go
@@ -0,0 +1,20 @@
+package buildinfo
+
+import (
+	"testing"
+
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func TestGetTemplate(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleGetSuccessfully(t, GetOutput)
+
+	actual, err := Get(fake.ServiceClient()).Extract()
+	th.AssertNoErr(t, err)
+
+	expected := GetExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
diff --git a/openstack/orchestration/v1/buildinfo/results.go b/openstack/orchestration/v1/buildinfo/results.go
new file mode 100644
index 0000000..683a434
--- /dev/null
+++ b/openstack/orchestration/v1/buildinfo/results.go
@@ -0,0 +1,37 @@
+package buildinfo
+
+import (
+	"github.com/mitchellh/mapstructure"
+	"github.com/rackspace/gophercloud"
+)
+
+// Revision represents the API/Engine revision of a Heat deployment.
+type Revision struct {
+	Revision string `mapstructure:"revision"`
+}
+
+// BuildInfo represents the build information for a Heat deployment.
+type BuildInfo struct {
+	API    Revision `mapstructure:"api"`
+	Engine Revision `mapstructure:"engine"`
+}
+
+// GetResult represents the result of a Get operation.
+type GetResult struct {
+	gophercloud.Result
+}
+
+// Extract returns a pointer to a BuildInfo object and is called after a
+// Get operation.
+func (r GetResult) Extract() (*BuildInfo, error) {
+	if r.Err != nil {
+		return nil, r.Err
+	}
+
+	var res BuildInfo
+	if err := mapstructure.Decode(r.Body, &res); err != nil {
+		return nil, err
+	}
+
+	return &res, nil
+}
diff --git a/openstack/orchestration/v1/buildinfo/urls.go b/openstack/orchestration/v1/buildinfo/urls.go
new file mode 100644
index 0000000..2c873d0
--- /dev/null
+++ b/openstack/orchestration/v1/buildinfo/urls.go
@@ -0,0 +1,7 @@
+package buildinfo
+
+import "github.com/rackspace/gophercloud"
+
+func getURL(c *gophercloud.ServiceClient) string {
+	return c.ServiceURL("build_info")
+}
diff --git a/openstack/orchestration/v1/stackevents/doc.go b/openstack/orchestration/v1/stackevents/doc.go
new file mode 100644
index 0000000..51cdd97
--- /dev/null
+++ b/openstack/orchestration/v1/stackevents/doc.go
@@ -0,0 +1,4 @@
+// Package stackevents provides operations for finding, listing, and retrieving
+// stack events. Stack events are events that take place on stacks such as
+// updating and abandoning.
+package stackevents
diff --git a/openstack/orchestration/v1/stackevents/fixtures.go b/openstack/orchestration/v1/stackevents/fixtures.go
new file mode 100644
index 0000000..016ae00
--- /dev/null
+++ b/openstack/orchestration/v1/stackevents/fixtures.go
@@ -0,0 +1,446 @@
+package stackevents
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+	"time"
+
+	"github.com/rackspace/gophercloud"
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+// FindExpected represents the expected object from a Find request.
+var FindExpected = []Event{
+	Event{
+		ResourceName: "hello_world",
+		Time:         time.Date(2015, 2, 5, 21, 33, 11, 0, time.UTC),
+		Links: []gophercloud.Link{
+			gophercloud.Link{
+				Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/06feb26f-9298-4a9b-8749-9d770e5d577a",
+				Rel:  "self",
+			},
+			gophercloud.Link{
+				Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+				Rel:  "resource",
+			},
+			gophercloud.Link{
+				Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+				Rel:  "stack",
+			},
+		},
+		LogicalResourceID:    "hello_world",
+		ResourceStatusReason: "state changed",
+		ResourceStatus:       "CREATE_IN_PROGRESS",
+		PhysicalResourceID:   "",
+		ID:                   "06feb26f-9298-4a9b-8749-9d770e5d577a",
+	},
+	Event{
+		ResourceName: "hello_world",
+		Time:         time.Date(2015, 2, 5, 21, 33, 27, 0, time.UTC),
+		Links: []gophercloud.Link{
+			gophercloud.Link{
+				Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/93940999-7d40-44ae-8de4-19624e7b8d18",
+				Rel:  "self",
+			},
+			gophercloud.Link{
+				Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+				Rel:  "resource",
+			},
+			gophercloud.Link{
+				Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+				Rel:  "stack",
+			},
+		},
+		LogicalResourceID:    "hello_world",
+		ResourceStatusReason: "state changed",
+		ResourceStatus:       "CREATE_COMPLETE",
+		PhysicalResourceID:   "49181cd6-169a-4130-9455-31185bbfc5bf",
+		ID:                   "93940999-7d40-44ae-8de4-19624e7b8d18",
+	},
+}
+
+// FindOutput represents the response body from a Find request.
+const FindOutput = `
+{
+  "events": [
+  {
+    "resource_name": "hello_world",
+    "event_time": "2015-02-05T21:33:11Z",
+    "links": [
+    {
+      "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/06feb26f-9298-4a9b-8749-9d770e5d577a",
+      "rel": "self"
+    },
+    {
+      "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+      "rel": "resource"
+    },
+    {
+      "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+      "rel": "stack"
+    }
+    ],
+    "logical_resource_id": "hello_world",
+    "resource_status_reason": "state changed",
+    "resource_status": "CREATE_IN_PROGRESS",
+    "physical_resource_id": null,
+    "id": "06feb26f-9298-4a9b-8749-9d770e5d577a"
+    },
+    {
+      "resource_name": "hello_world",
+      "event_time": "2015-02-05T21:33:27Z",
+      "links": [
+      {
+        "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/93940999-7d40-44ae-8de4-19624e7b8d18",
+        "rel": "self"
+      },
+      {
+        "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+        "rel": "resource"
+      },
+      {
+        "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+        "rel": "stack"
+      }
+      ],
+      "logical_resource_id": "hello_world",
+      "resource_status_reason": "state changed",
+      "resource_status": "CREATE_COMPLETE",
+      "physical_resource_id": "49181cd6-169a-4130-9455-31185bbfc5bf",
+      "id": "93940999-7d40-44ae-8de4-19624e7b8d18"
+    }
+  ]
+}`
+
+// HandleFindSuccessfully creates an HTTP handler at `/stacks/postman_stack/events`
+// on the test handler mux that responds with a `Find` response.
+func HandleFindSuccessfully(t *testing.T, output string) {
+	th.Mux.HandleFunc("/stacks/postman_stack/events", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+
+		w.Header().Set("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprintf(w, output)
+	})
+}
+
+// ListExpected represents the expected object from a List request.
+var ListExpected = []Event{
+	Event{
+		ResourceName: "hello_world",
+		Time:         time.Date(2015, 2, 5, 21, 33, 11, 0, time.UTC),
+		Links: []gophercloud.Link{
+			gophercloud.Link{
+				Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/06feb26f-9298-4a9b-8749-9d770e5d577a",
+				Rel:  "self",
+			},
+			gophercloud.Link{
+				Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+				Rel:  "resource",
+			},
+			gophercloud.Link{
+				Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+				Rel:  "stack",
+			},
+		},
+		LogicalResourceID:    "hello_world",
+		ResourceStatusReason: "state changed",
+		ResourceStatus:       "CREATE_IN_PROGRESS",
+		PhysicalResourceID:   "",
+		ID:                   "06feb26f-9298-4a9b-8749-9d770e5d577a",
+	},
+	Event{
+		ResourceName: "hello_world",
+		Time:         time.Date(2015, 2, 5, 21, 33, 27, 0, time.UTC),
+		Links: []gophercloud.Link{
+			gophercloud.Link{
+				Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/93940999-7d40-44ae-8de4-19624e7b8d18",
+				Rel:  "self",
+			},
+			gophercloud.Link{
+				Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+				Rel:  "resource",
+			},
+			gophercloud.Link{
+				Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+				Rel:  "stack",
+			},
+		},
+		LogicalResourceID:    "hello_world",
+		ResourceStatusReason: "state changed",
+		ResourceStatus:       "CREATE_COMPLETE",
+		PhysicalResourceID:   "49181cd6-169a-4130-9455-31185bbfc5bf",
+		ID:                   "93940999-7d40-44ae-8de4-19624e7b8d18",
+	},
+}
+
+// ListOutput represents the response body from a List request.
+const ListOutput = `
+{
+  "events": [
+  {
+    "resource_name": "hello_world",
+    "event_time": "2015-02-05T21:33:11Z",
+    "links": [
+    {
+      "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/06feb26f-9298-4a9b-8749-9d770e5d577a",
+      "rel": "self"
+    },
+    {
+      "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+      "rel": "resource"
+    },
+    {
+      "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+      "rel": "stack"
+    }
+    ],
+    "logical_resource_id": "hello_world",
+    "resource_status_reason": "state changed",
+    "resource_status": "CREATE_IN_PROGRESS",
+    "physical_resource_id": null,
+    "id": "06feb26f-9298-4a9b-8749-9d770e5d577a"
+    },
+    {
+      "resource_name": "hello_world",
+      "event_time": "2015-02-05T21:33:27Z",
+      "links": [
+      {
+        "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/93940999-7d40-44ae-8de4-19624e7b8d18",
+        "rel": "self"
+      },
+      {
+        "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+        "rel": "resource"
+      },
+      {
+        "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+        "rel": "stack"
+      }
+      ],
+      "logical_resource_id": "hello_world",
+      "resource_status_reason": "state changed",
+      "resource_status": "CREATE_COMPLETE",
+      "physical_resource_id": "49181cd6-169a-4130-9455-31185bbfc5bf",
+      "id": "93940999-7d40-44ae-8de4-19624e7b8d18"
+    }
+  ]
+}`
+
+// HandleListSuccessfully creates an HTTP handler at `/stacks/hello_world/49181cd6-169a-4130-9455-31185bbfc5bf/events`
+// on the test handler mux that responds with a `List` response.
+func HandleListSuccessfully(t *testing.T, output string) {
+	th.Mux.HandleFunc("/stacks/hello_world/49181cd6-169a-4130-9455-31185bbfc5bf/events", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+
+		w.Header().Set("Content-Type", "application/json")
+		r.ParseForm()
+		marker := r.Form.Get("marker")
+		switch marker {
+		case "":
+			fmt.Fprintf(w, output)
+		case "93940999-7d40-44ae-8de4-19624e7b8d18":
+			fmt.Fprintf(w, `{"events":[]}`)
+		default:
+			t.Fatalf("Unexpected marker: [%s]", marker)
+		}
+	})
+}
+
+// ListResourceEventsExpected represents the expected object from a ListResourceEvents request.
+var ListResourceEventsExpected = []Event{
+	Event{
+		ResourceName: "hello_world",
+		Time:         time.Date(2015, 2, 5, 21, 33, 11, 0, time.UTC),
+		Links: []gophercloud.Link{
+			gophercloud.Link{
+				Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/06feb26f-9298-4a9b-8749-9d770e5d577a",
+				Rel:  "self",
+			},
+			gophercloud.Link{
+				Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+				Rel:  "resource",
+			},
+			gophercloud.Link{
+				Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+				Rel:  "stack",
+			},
+		},
+		LogicalResourceID:    "hello_world",
+		ResourceStatusReason: "state changed",
+		ResourceStatus:       "CREATE_IN_PROGRESS",
+		PhysicalResourceID:   "",
+		ID:                   "06feb26f-9298-4a9b-8749-9d770e5d577a",
+	},
+	Event{
+		ResourceName: "hello_world",
+		Time:         time.Date(2015, 2, 5, 21, 33, 27, 0, time.UTC),
+		Links: []gophercloud.Link{
+			gophercloud.Link{
+				Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/93940999-7d40-44ae-8de4-19624e7b8d18",
+				Rel:  "self",
+			},
+			gophercloud.Link{
+				Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+				Rel:  "resource",
+			},
+			gophercloud.Link{
+				Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+				Rel:  "stack",
+			},
+		},
+		LogicalResourceID:    "hello_world",
+		ResourceStatusReason: "state changed",
+		ResourceStatus:       "CREATE_COMPLETE",
+		PhysicalResourceID:   "49181cd6-169a-4130-9455-31185bbfc5bf",
+		ID:                   "93940999-7d40-44ae-8de4-19624e7b8d18",
+	},
+}
+
+// ListResourceEventsOutput represents the response body from a ListResourceEvents request.
+const ListResourceEventsOutput = `
+{
+  "events": [
+  {
+    "resource_name": "hello_world",
+    "event_time": "2015-02-05T21:33:11Z",
+    "links": [
+    {
+      "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/06feb26f-9298-4a9b-8749-9d770e5d577a",
+      "rel": "self"
+    },
+    {
+      "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+      "rel": "resource"
+    },
+    {
+      "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+      "rel": "stack"
+    }
+    ],
+    "logical_resource_id": "hello_world",
+    "resource_status_reason": "state changed",
+    "resource_status": "CREATE_IN_PROGRESS",
+    "physical_resource_id": null,
+    "id": "06feb26f-9298-4a9b-8749-9d770e5d577a"
+    },
+    {
+      "resource_name": "hello_world",
+      "event_time": "2015-02-05T21:33:27Z",
+      "links": [
+      {
+        "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/93940999-7d40-44ae-8de4-19624e7b8d18",
+        "rel": "self"
+      },
+      {
+        "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+        "rel": "resource"
+      },
+      {
+        "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+        "rel": "stack"
+      }
+      ],
+      "logical_resource_id": "hello_world",
+      "resource_status_reason": "state changed",
+      "resource_status": "CREATE_COMPLETE",
+      "physical_resource_id": "49181cd6-169a-4130-9455-31185bbfc5bf",
+      "id": "93940999-7d40-44ae-8de4-19624e7b8d18"
+    }
+  ]
+}`
+
+// HandleListResourceEventsSuccessfully creates an HTTP handler at `/stacks/hello_world/49181cd6-169a-4130-9455-31185bbfc5bf/resources/my_resource/events`
+// on the test handler mux that responds with a `ListResourceEvents` response.
+func HandleListResourceEventsSuccessfully(t *testing.T, output string) {
+	th.Mux.HandleFunc("/stacks/hello_world/49181cd6-169a-4130-9455-31185bbfc5bf/resources/my_resource/events", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+
+		w.Header().Set("Content-Type", "application/json")
+		r.ParseForm()
+		marker := r.Form.Get("marker")
+		switch marker {
+		case "":
+			fmt.Fprintf(w, output)
+		case "93940999-7d40-44ae-8de4-19624e7b8d18":
+			fmt.Fprintf(w, `{"events":[]}`)
+		default:
+			t.Fatalf("Unexpected marker: [%s]", marker)
+		}
+	})
+}
+
+// GetExpected represents the expected object from a Get request.
+var GetExpected = &Event{
+	ResourceName: "hello_world",
+	Time:         time.Date(2015, 2, 5, 21, 33, 27, 0, time.UTC),
+	Links: []gophercloud.Link{
+		gophercloud.Link{
+			Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/93940999-7d40-44ae-8de4-19624e7b8d18",
+			Rel:  "self",
+		},
+		gophercloud.Link{
+			Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+			Rel:  "resource",
+		},
+		gophercloud.Link{
+			Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+			Rel:  "stack",
+		},
+	},
+	LogicalResourceID:    "hello_world",
+	ResourceStatusReason: "state changed",
+	ResourceStatus:       "CREATE_COMPLETE",
+	PhysicalResourceID:   "49181cd6-169a-4130-9455-31185bbfc5bf",
+	ID:                   "93940999-7d40-44ae-8de4-19624e7b8d18",
+}
+
+// GetOutput represents the response body from a Get request.
+const GetOutput = `
+{
+  "event":{
+    "resource_name": "hello_world",
+    "event_time": "2015-02-05T21:33:27Z",
+    "links": [
+    {
+      "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world/events/93940999-7d40-44ae-8de4-19624e7b8d18",
+      "rel": "self"
+    },
+    {
+      "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+      "rel": "resource"
+    },
+    {
+      "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+      "rel": "stack"
+    }
+    ],
+    "logical_resource_id": "hello_world",
+    "resource_status_reason": "state changed",
+    "resource_status": "CREATE_COMPLETE",
+    "physical_resource_id": "49181cd6-169a-4130-9455-31185bbfc5bf",
+    "id": "93940999-7d40-44ae-8de4-19624e7b8d18"
+  }
+}`
+
+// HandleGetSuccessfully creates an HTTP handler at `/stacks/hello_world/49181cd6-169a-4130-9455-31185bbfc5bf/resources/my_resource/events/93940999-7d40-44ae-8de4-19624e7b8d18`
+// on the test handler mux that responds with a `Get` response.
+func HandleGetSuccessfully(t *testing.T, output string) {
+	th.Mux.HandleFunc("/stacks/hello_world/49181cd6-169a-4130-9455-31185bbfc5bf/resources/my_resource/events/93940999-7d40-44ae-8de4-19624e7b8d18", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+
+		w.Header().Set("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprintf(w, output)
+	})
+}
diff --git a/openstack/orchestration/v1/stackevents/requests.go b/openstack/orchestration/v1/stackevents/requests.go
new file mode 100644
index 0000000..80808b1
--- /dev/null
+++ b/openstack/orchestration/v1/stackevents/requests.go
@@ -0,0 +1,208 @@
+package stackevents
+
+import (
+	"github.com/racker/perigee"
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/pagination"
+)
+
+// Find retrieves stack events for the given stack name.
+func Find(c *gophercloud.ServiceClient, stackName string) FindResult {
+	var res FindResult
+
+	_, res.Err = perigee.Request("GET", findURL(c, stackName), perigee.Options{
+		MoreHeaders: c.AuthenticatedHeaders(),
+		Results:     &res.Body,
+		OkCodes:     []int{200},
+	})
+	return res
+}
+
+// SortDir is a type for specifying in which direction to sort a list of events.
+type SortDir string
+
+// SortKey is a type for specifying by which key to sort a list of events.
+type SortKey string
+
+// ResourceStatus is a type for specifying by which resource status to filter a
+// list of events.
+type ResourceStatus string
+
+// ResourceAction is a type for specifying by which resource action to filter a
+// list of events.
+type ResourceAction string
+
+var (
+	// ResourceStatusInProgress is used to filter a List request by the 'IN_PROGRESS' status.
+	ResourceStatusInProgress ResourceStatus = "IN_PROGRESS"
+	// ResourceStatusComplete is used to filter a List request by the 'COMPLETE' status.
+	ResourceStatusComplete ResourceStatus = "COMPLETE"
+	// ResourceStatusFailed is used to filter a List request by the 'FAILED' status.
+	ResourceStatusFailed ResourceStatus = "FAILED"
+
+	// ResourceActionCreate is used to filter a List request by the 'CREATE' action.
+	ResourceActionCreate ResourceAction = "CREATE"
+	// ResourceActionDelete is used to filter a List request by the 'DELETE' action.
+	ResourceActionDelete ResourceAction = "DELETE"
+	// ResourceActionUpdate is used to filter a List request by the 'UPDATE' action.
+	ResourceActionUpdate ResourceAction = "UPDATE"
+	// ResourceActionRollback is used to filter a List request by the 'ROLLBACK' action.
+	ResourceActionRollback ResourceAction = "ROLLBACK"
+	// ResourceActionSuspend is used to filter a List request by the 'SUSPEND' action.
+	ResourceActionSuspend ResourceAction = "SUSPEND"
+	// ResourceActionResume is used to filter a List request by the 'RESUME' action.
+	ResourceActionResume ResourceAction = "RESUME"
+	// ResourceActionAbandon is used to filter a List request by the 'ABANDON' action.
+	ResourceActionAbandon ResourceAction = "ABANDON"
+
+	// SortAsc is used to sort a list of stacks in ascending order.
+	SortAsc SortDir = "asc"
+	// SortDesc is used to sort a list of stacks in descending order.
+	SortDesc SortDir = "desc"
+
+	// SortName is used to sort a list of stacks by name.
+	SortName SortKey = "name"
+	// SortResourceType is used to sort a list of stacks by resource type.
+	SortResourceType SortKey = "resource_type"
+	// SortCreatedAt is used to sort a list of stacks by date created.
+	SortCreatedAt SortKey = "created_at"
+)
+
+// ListOptsBuilder allows extensions to add additional parameters to the
+// List request.
+type ListOptsBuilder interface {
+	ToStackEventListQuery() (string, error)
+}
+
+// ListOpts allows the filtering and sorting of paginated collections through
+// the API. Marker and Limit are used for pagination.
+type ListOpts struct {
+	// The stack resource ID with which to start the listing.
+	Marker string `q:"marker"`
+	// Integer value for the limit of values to return.
+	Limit int `q:"limit"`
+	// Filters the event list by the specified ResourceAction. You can use this
+	// filter multiple times to filter by multiple resource actions: CREATE, DELETE,
+	// UPDATE, ROLLBACK, SUSPEND, RESUME or ADOPT.
+	ResourceActions []ResourceAction `q:"resource_action"`
+	// Filters the event list by the specified resource_status. You can use this
+	// filter multiple times to filter by multiple resource statuses: IN_PROGRESS,
+	// COMPLETE or FAILED.
+	ResourceStatuses []ResourceStatus `q:"resource_status"`
+	// Filters the event list by the specified resource_name. You can use this
+	// filter multiple times to filter by multiple resource names.
+	ResourceNames []string `q:"resource_name"`
+	// Filters the event list by the specified resource_type. You can use this
+	// filter multiple times to filter by multiple resource types: OS::Nova::Server,
+	// OS::Cinder::Volume, and so on.
+	ResourceTypes []string `q:"resource_type"`
+	// Sorts the event list by: resource_type or created_at.
+	SortKey SortKey `q:"sort_keys"`
+	// The sort direction of the event list. Which is asc (ascending) or desc (descending).
+	SortDir SortDir `q:"sort_dir"`
+}
+
+// ToStackEventListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToStackEventListQuery() (string, error) {
+	q, err := gophercloud.BuildQueryString(opts)
+	if err != nil {
+		return "", err
+	}
+	return q.String(), nil
+}
+
+// List makes a request against the API to list resources for the given stack.
+func List(client *gophercloud.ServiceClient, stackName, stackID string, opts ListOptsBuilder) pagination.Pager {
+	url := listURL(client, stackName, stackID)
+
+	if opts != nil {
+		query, err := opts.ToStackEventListQuery()
+		if err != nil {
+			return pagination.Pager{Err: err}
+		}
+		url += query
+	}
+
+	createPageFn := func(r pagination.PageResult) pagination.Page {
+		p := EventPage{pagination.MarkerPageBase{PageResult: r}}
+		p.MarkerPageBase.Owner = p
+		return p
+	}
+
+	return pagination.NewPager(client, url, createPageFn)
+}
+
+// ListResourceEventsOptsBuilder allows extensions to add additional parameters to the
+// ListResourceEvents request.
+type ListResourceEventsOptsBuilder interface {
+	ToResourceEventListQuery() (string, error)
+}
+
+// ListResourceEventsOpts allows the filtering and sorting of paginated resource events through
+// the API. Marker and Limit are used for pagination.
+type ListResourceEventsOpts struct {
+	// The stack resource ID with which to start the listing.
+	Marker string `q:"marker"`
+	// Integer value for the limit of values to return.
+	Limit int `q:"limit"`
+	// Filters the event list by the specified ResourceAction. You can use this
+	// filter multiple times to filter by multiple resource actions: CREATE, DELETE,
+	// UPDATE, ROLLBACK, SUSPEND, RESUME or ADOPT.
+	ResourceActions []string `q:"resource_action"`
+	// Filters the event list by the specified resource_status. You can use this
+	// filter multiple times to filter by multiple resource statuses: IN_PROGRESS,
+	// COMPLETE or FAILED.
+	ResourceStatuses []string `q:"resource_status"`
+	// Filters the event list by the specified resource_name. You can use this
+	// filter multiple times to filter by multiple resource names.
+	ResourceNames []string `q:"resource_name"`
+	// Filters the event list by the specified resource_type. You can use this
+	// filter multiple times to filter by multiple resource types: OS::Nova::Server,
+	// OS::Cinder::Volume, and so on.
+	ResourceTypes []string `q:"resource_type"`
+	// Sorts the event list by: resource_type or created_at.
+	SortKey SortKey `q:"sort_keys"`
+	// The sort direction of the event list. Which is asc (ascending) or desc (descending).
+	SortDir SortDir `q:"sort_dir"`
+}
+
+// ToResourceEventsListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToResourceEventsListQuery() (string, error) {
+	q, err := gophercloud.BuildQueryString(opts)
+	if err != nil {
+		return "", err
+	}
+	return q.String(), nil
+}
+
+// ListResourceEvents makes a request against the API to list resources for the given stack.
+func ListResourceEvents(client *gophercloud.ServiceClient, stackName, stackID, resourceName string, opts ListResourceEventsOptsBuilder) pagination.Pager {
+	url := listResourceEventsURL(client, stackName, stackID, resourceName)
+
+	if opts != nil {
+		query, err := opts.ToResourceEventListQuery()
+		if err != nil {
+			return pagination.Pager{Err: err}
+		}
+		url += query
+	}
+
+	createPageFn := func(r pagination.PageResult) pagination.Page {
+		p := EventPage{pagination.MarkerPageBase{PageResult: r}}
+		p.MarkerPageBase.Owner = p
+		return p
+	}
+
+	return pagination.NewPager(client, url, createPageFn)
+}
+
+// Get retreives data for the given stack resource.
+func Get(c *gophercloud.ServiceClient, stackName, stackID, resourceName, eventID string) GetResult {
+	var res GetResult
+	_, res.Err = perigee.Request("GET", getURL(c, stackName, stackID, resourceName, eventID), perigee.Options{
+		MoreHeaders: c.AuthenticatedHeaders(),
+		Results:     &res.Body,
+		OkCodes:     []int{200},
+	})
+	return res
+}
diff --git a/openstack/orchestration/v1/stackevents/requests_test.go b/openstack/orchestration/v1/stackevents/requests_test.go
new file mode 100644
index 0000000..a4da4d0
--- /dev/null
+++ b/openstack/orchestration/v1/stackevents/requests_test.go
@@ -0,0 +1,71 @@
+package stackevents
+
+import (
+	"testing"
+
+	"github.com/rackspace/gophercloud/pagination"
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func TestFindEvents(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleFindSuccessfully(t, FindOutput)
+
+	actual, err := Find(fake.ServiceClient(), "postman_stack").Extract()
+	th.AssertNoErr(t, err)
+
+	expected := FindExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
+
+func TestList(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleListSuccessfully(t, ListOutput)
+
+	count := 0
+	err := List(fake.ServiceClient(), "hello_world", "49181cd6-169a-4130-9455-31185bbfc5bf", nil).EachPage(func(page pagination.Page) (bool, error) {
+		count++
+		actual, err := ExtractEvents(page)
+		th.AssertNoErr(t, err)
+
+		th.CheckDeepEquals(t, ListExpected, actual)
+
+		return true, nil
+	})
+	th.AssertNoErr(t, err)
+	th.CheckEquals(t, count, 1)
+}
+
+func TestListResourceEvents(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleListResourceEventsSuccessfully(t, ListResourceEventsOutput)
+
+	count := 0
+	err := ListResourceEvents(fake.ServiceClient(), "hello_world", "49181cd6-169a-4130-9455-31185bbfc5bf", "my_resource", nil).EachPage(func(page pagination.Page) (bool, error) {
+		count++
+		actual, err := ExtractEvents(page)
+		th.AssertNoErr(t, err)
+
+		th.CheckDeepEquals(t, ListResourceEventsExpected, actual)
+
+		return true, nil
+	})
+	th.AssertNoErr(t, err)
+	th.CheckEquals(t, count, 1)
+}
+
+func TestGetEvent(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleGetSuccessfully(t, GetOutput)
+
+	actual, err := Get(fake.ServiceClient(), "hello_world", "49181cd6-169a-4130-9455-31185bbfc5bf", "my_resource", "93940999-7d40-44ae-8de4-19624e7b8d18").Extract()
+	th.AssertNoErr(t, err)
+
+	expected := GetExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
diff --git a/openstack/orchestration/v1/stackevents/results.go b/openstack/orchestration/v1/stackevents/results.go
new file mode 100644
index 0000000..bf233ae
--- /dev/null
+++ b/openstack/orchestration/v1/stackevents/results.go
@@ -0,0 +1,162 @@
+package stackevents
+
+import (
+	"time"
+
+	"github.com/mitchellh/mapstructure"
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/pagination"
+)
+
+// Event represents a stack event.
+type Event struct {
+	// The name of the resource for which the event occurred.
+	ResourceName string `mapstructure:"resource_name"`
+	// The time the event occurred.
+	Time time.Time `mapstructure:"-"`
+	// The URLs to the event.
+	Links []gophercloud.Link `mapstructure:"links"`
+	// The logical ID of the stack resource.
+	LogicalResourceID string `mapstructure:"logical_resource_id"`
+	// The reason of the status of the event.
+	ResourceStatusReason string `mapstructure:"resource_status_reason"`
+	// The status of the event.
+	ResourceStatus string `mapstructure:"resource_status"`
+	// The physical ID of the stack resource.
+	PhysicalResourceID string `mapstructure:"physical_resource_id"`
+	// The event ID.
+	ID string `mapstructure:"id"`
+	// Properties of the stack resource.
+	ResourceProperties map[string]interface{} `mapstructure:"resource_properties"`
+}
+
+// FindResult represents the result of a Find operation.
+type FindResult struct {
+	gophercloud.Result
+}
+
+// Extract returns a slice of Event objects and is called after a
+// Find operation.
+func (r FindResult) Extract() ([]Event, error) {
+	if r.Err != nil {
+		return nil, r.Err
+	}
+
+	var res struct {
+		Res []Event `mapstructure:"events"`
+	}
+
+	if err := mapstructure.Decode(r.Body, &res); err != nil {
+		return nil, err
+	}
+
+	events := r.Body.(map[string]interface{})["events"].([]interface{})
+
+	for i, eventRaw := range events {
+		event := eventRaw.(map[string]interface{})
+		if date, ok := event["event_time"]; ok && date != nil {
+			t, err := time.Parse(time.RFC3339, date.(string))
+			if err != nil {
+				return nil, err
+			}
+			res.Res[i].Time = t
+		}
+	}
+
+	return res.Res, nil
+}
+
+// EventPage abstracts the raw results of making a List() request against the API.
+// As OpenStack extensions may freely alter the response bodies of structures returned to the client, you may only safely access the
+// data provided through the ExtractResources call.
+type EventPage struct {
+	pagination.MarkerPageBase
+}
+
+// IsEmpty returns true if a page contains no Server results.
+func (r EventPage) IsEmpty() (bool, error) {
+	events, err := ExtractEvents(r)
+	if err != nil {
+		return true, err
+	}
+	return len(events) == 0, nil
+}
+
+// LastMarker returns the last stack ID in a ListResult.
+func (r EventPage) LastMarker() (string, error) {
+	events, err := ExtractEvents(r)
+	if err != nil {
+		return "", err
+	}
+	if len(events) == 0 {
+		return "", nil
+	}
+	return events[len(events)-1].ID, nil
+}
+
+// ExtractEvents interprets the results of a single page from a List() call, producing a slice of Event entities.
+func ExtractEvents(page pagination.Page) ([]Event, error) {
+	casted := page.(EventPage).Body
+
+	var res struct {
+		Res []Event `mapstructure:"events"`
+	}
+
+	if err := mapstructure.Decode(casted, &res); err != nil {
+		return nil, err
+	}
+
+	events := casted.(map[string]interface{})["events"].([]interface{})
+
+	for i, eventRaw := range events {
+		event := eventRaw.(map[string]interface{})
+		if date, ok := event["event_time"]; ok && date != nil {
+			t, err := time.Parse(time.RFC3339, date.(string))
+			if err != nil {
+				return nil, err
+			}
+			res.Res[i].Time = t
+		}
+	}
+
+	return res.Res, nil
+}
+
+// ExtractResourceEvents interprets the results of a single page from a
+// ListResourceEvents() call, producing a slice of Event entities.
+func ExtractResourceEvents(page pagination.Page) ([]Event, error) {
+	return ExtractEvents(page)
+}
+
+// GetResult represents the result of a Get operation.
+type GetResult struct {
+	gophercloud.Result
+}
+
+// Extract returns a pointer to an Event object and is called after a
+// Get operation.
+func (r GetResult) Extract() (*Event, error) {
+	if r.Err != nil {
+		return nil, r.Err
+	}
+
+	var res struct {
+		Res *Event `mapstructure:"event"`
+	}
+
+	if err := mapstructure.Decode(r.Body, &res); err != nil {
+		return nil, err
+	}
+
+	event := r.Body.(map[string]interface{})["event"].(map[string]interface{})
+
+	if date, ok := event["event_time"]; ok && date != nil {
+		t, err := time.Parse(time.RFC3339, date.(string))
+		if err != nil {
+			return nil, err
+		}
+		res.Res.Time = t
+	}
+
+	return res.Res, nil
+}
diff --git a/openstack/orchestration/v1/stackevents/urls.go b/openstack/orchestration/v1/stackevents/urls.go
new file mode 100644
index 0000000..8b5eceb
--- /dev/null
+++ b/openstack/orchestration/v1/stackevents/urls.go
@@ -0,0 +1,19 @@
+package stackevents
+
+import "github.com/rackspace/gophercloud"
+
+func findURL(c *gophercloud.ServiceClient, stackName string) string {
+	return c.ServiceURL("stacks", stackName, "events")
+}
+
+func listURL(c *gophercloud.ServiceClient, stackName, stackID string) string {
+	return c.ServiceURL("stacks", stackName, stackID, "events")
+}
+
+func listResourceEventsURL(c *gophercloud.ServiceClient, stackName, stackID, resourceName string) string {
+	return c.ServiceURL("stacks", stackName, stackID, "resources", resourceName, "events")
+}
+
+func getURL(c *gophercloud.ServiceClient, stackName, stackID, resourceName, eventID string) string {
+	return c.ServiceURL("stacks", stackName, stackID, "resources", resourceName, "events", eventID)
+}
diff --git a/openstack/orchestration/v1/stackresources/doc.go b/openstack/orchestration/v1/stackresources/doc.go
new file mode 100644
index 0000000..e4f8b08
--- /dev/null
+++ b/openstack/orchestration/v1/stackresources/doc.go
@@ -0,0 +1,5 @@
+// Package stackresources provides operations for working with stack resources.
+// A resource is a template artifact that represents some component of your
+// desired architecture (a Cloud Server, a group of scaled Cloud Servers, a load
+// balancer, some configuration management system, and so forth).
+package stackresources
diff --git a/openstack/orchestration/v1/stackresources/fixtures.go b/openstack/orchestration/v1/stackresources/fixtures.go
new file mode 100644
index 0000000..0b930f4
--- /dev/null
+++ b/openstack/orchestration/v1/stackresources/fixtures.go
@@ -0,0 +1,451 @@
+package stackresources
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+	"time"
+
+	"github.com/rackspace/gophercloud"
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+// FindExpected represents the expected object from a Find request.
+var FindExpected = []Resource{
+	Resource{
+		Name: "hello_world",
+		Links: []gophercloud.Link{
+			gophercloud.Link{
+				Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+				Rel:  "self",
+			},
+			gophercloud.Link{
+				Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+				Rel:  "stack",
+			},
+		},
+		LogicalID:    "hello_world",
+		StatusReason: "state changed",
+		UpdatedTime:  time.Date(2015, 2, 5, 21, 33, 11, 0, time.UTC),
+		RequiredBy:   []interface{}{},
+		Status:       "CREATE_IN_PROGRESS",
+		PhysicalID:   "49181cd6-169a-4130-9455-31185bbfc5bf",
+		Type:         "OS::Nova::Server",
+	},
+}
+
+// FindOutput represents the response body from a Find request.
+const FindOutput = `
+{
+  "resources": [
+  {
+    "resource_name": "hello_world",
+    "links": [
+      {
+      "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+      "rel": "self"
+      },
+      {
+        "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+        "rel": "stack"
+      }
+    ],
+    "logical_resource_id": "hello_world",
+    "resource_status_reason": "state changed",
+    "updated_time": "2015-02-05T21:33:11Z",
+    "required_by": [],
+    "resource_status": "CREATE_IN_PROGRESS",
+    "physical_resource_id": "49181cd6-169a-4130-9455-31185bbfc5bf",
+    "resource_type": "OS::Nova::Server"
+  }
+  ]
+}`
+
+// HandleFindSuccessfully creates an HTTP handler at `/stacks/hello_world/resources`
+// on the test handler mux that responds with a `Find` response.
+func HandleFindSuccessfully(t *testing.T, output string) {
+	th.Mux.HandleFunc("/stacks/hello_world/resources", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+
+		w.Header().Set("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprintf(w, output)
+	})
+}
+
+// ListExpected represents the expected object from a List request.
+var ListExpected = []Resource{
+	Resource{
+		Name: "hello_world",
+		Links: []gophercloud.Link{
+			gophercloud.Link{
+				Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+				Rel:  "self",
+			},
+			gophercloud.Link{
+				Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+				Rel:  "stack",
+			},
+		},
+		LogicalID:    "hello_world",
+		StatusReason: "state changed",
+		UpdatedTime:  time.Date(2015, 2, 5, 21, 33, 11, 0, time.UTC),
+		RequiredBy:   []interface{}{},
+		Status:       "CREATE_IN_PROGRESS",
+		PhysicalID:   "49181cd6-169a-4130-9455-31185bbfc5bf",
+		Type:         "OS::Nova::Server",
+	},
+}
+
+// ListOutput represents the response body from a List request.
+const ListOutput = `{
+  "resources": [
+  {
+    "resource_name": "hello_world",
+    "links": [
+    {
+      "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
+      "rel": "self"
+    },
+    {
+      "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
+      "rel": "stack"
+    }
+    ],
+    "logical_resource_id": "hello_world",
+    "resource_status_reason": "state changed",
+    "updated_time": "2015-02-05T21:33:11Z",
+    "required_by": [],
+    "resource_status": "CREATE_IN_PROGRESS",
+    "physical_resource_id": "49181cd6-169a-4130-9455-31185bbfc5bf",
+    "resource_type": "OS::Nova::Server"
+  }
+]
+}`
+
+// HandleListSuccessfully creates an HTTP handler at `/stacks/hello_world/49181cd6-169a-4130-9455-31185bbfc5bf/resources`
+// on the test handler mux that responds with a `List` response.
+func HandleListSuccessfully(t *testing.T, output string) {
+	th.Mux.HandleFunc("/stacks/hello_world/49181cd6-169a-4130-9455-31185bbfc5bf/resources", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+
+		w.Header().Set("Content-Type", "application/json")
+		r.ParseForm()
+		marker := r.Form.Get("marker")
+		switch marker {
+		case "":
+			fmt.Fprintf(w, output)
+		case "49181cd6-169a-4130-9455-31185bbfc5bf":
+			fmt.Fprintf(w, `{"resources":[]}`)
+		default:
+			t.Fatalf("Unexpected marker: [%s]", marker)
+		}
+	})
+}
+
+// GetExpected represents the expected object from a Get request.
+var GetExpected = &Resource{
+	Name: "wordpress_instance",
+	Links: []gophercloud.Link{
+		gophercloud.Link{
+			Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e/resources/wordpress_instance",
+			Rel:  "self",
+		},
+		gophercloud.Link{
+			Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e",
+			Rel:  "stack",
+		},
+	},
+	LogicalID:    "wordpress_instance",
+	StatusReason: "state changed",
+	UpdatedTime:  time.Date(2014, 12, 10, 18, 34, 35, 0, time.UTC),
+	RequiredBy:   []interface{}{},
+	Status:       "CREATE_COMPLETE",
+	PhysicalID:   "00e3a2fe-c65d-403c-9483-4db9930dd194",
+	Type:         "OS::Nova::Server",
+}
+
+// GetOutput represents the response body from a Get request.
+const GetOutput = `
+{
+  "resource": {
+    "resource_name": "wordpress_instance",
+    "description": "",
+    "links": [
+    {
+      "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e/resources/wordpress_instance",
+      "rel": "self"
+    },
+    {
+      "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e",
+      "rel": "stack"
+    }
+    ],
+    "logical_resource_id": "wordpress_instance",
+    "resource_status": "CREATE_COMPLETE",
+    "updated_time": "2014-12-10T18:34:35Z",
+    "required_by": [],
+    "resource_status_reason": "state changed",
+    "physical_resource_id": "00e3a2fe-c65d-403c-9483-4db9930dd194",
+    "resource_type": "OS::Nova::Server"
+  }
+}`
+
+// HandleGetSuccessfully creates an HTTP handler at `/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e/resources/wordpress_instance`
+// on the test handler mux that responds with a `Get` response.
+func HandleGetSuccessfully(t *testing.T, output string) {
+	th.Mux.HandleFunc("/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e/resources/wordpress_instance", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+
+		w.Header().Set("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprintf(w, output)
+	})
+}
+
+// MetadataExpected represents the expected object from a Metadata request.
+var MetadataExpected = map[string]string{
+	"number": "7",
+	"animal": "auk",
+}
+
+// MetadataOutput represents the response body from a Metadata request.
+const MetadataOutput = `
+{
+    "metadata": {
+      "number": "7",
+      "animal": "auk"
+    }
+}`
+
+// HandleMetadataSuccessfully creates an HTTP handler at `/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e/resources/wordpress_instance/metadata`
+// on the test handler mux that responds with a `Metadata` response.
+func HandleMetadataSuccessfully(t *testing.T, output string) {
+	th.Mux.HandleFunc("/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e/resources/wordpress_instance/metadata", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+
+		w.Header().Set("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprintf(w, output)
+	})
+}
+
+// ListTypesExpected represents the expected object from a ListTypes request.
+var ListTypesExpected = []string{
+	"OS::Nova::Server",
+	"OS::Heat::RandomString",
+	"OS::Swift::Container",
+	"OS::Trove::Instance",
+	"OS::Nova::FloatingIPAssociation",
+	"OS::Cinder::VolumeAttachment",
+	"OS::Nova::FloatingIP",
+	"OS::Nova::KeyPair",
+}
+
+// ListTypesOutput represents the response body from a ListTypes request.
+const ListTypesOutput = `
+{
+  "resource_types": [
+    "OS::Nova::Server",
+    "OS::Heat::RandomString",
+    "OS::Swift::Container",
+    "OS::Trove::Instance",
+    "OS::Nova::FloatingIPAssociation",
+    "OS::Cinder::VolumeAttachment",
+    "OS::Nova::FloatingIP",
+    "OS::Nova::KeyPair"
+  ]
+}`
+
+// HandleListTypesSuccessfully creates an HTTP handler at `/resource_types`
+// on the test handler mux that responds with a `ListTypes` response.
+func HandleListTypesSuccessfully(t *testing.T, output string) {
+	th.Mux.HandleFunc("/resource_types", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+
+		w.Header().Set("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprintf(w, output)
+	})
+}
+
+// GetSchemaExpected represents the expected object from a Schema request.
+var GetSchemaExpected = &TypeSchema{
+	Attributes: map[string]interface{}{
+		"an_attribute": map[string]interface{}{
+			"description": "An attribute description .",
+		},
+	},
+	Properties: map[string]interface{}{
+		"a_property": map[string]interface{}{
+			"update_allowed": false,
+			"required":       true,
+			"type":           "string",
+			"description":    "A resource description.",
+		},
+	},
+	ResourceType: "OS::Heat::AResourceName",
+}
+
+// GetSchemaOutput represents the response body from a Schema request.
+const GetSchemaOutput = `
+{
+  "attributes": {
+    "an_attribute": {
+      "description": "An attribute description ."
+    }
+  },
+  "properties": {
+    "a_property": {
+      "update_allowed": false,
+      "required": true,
+      "type": "string",
+      "description": "A resource description."
+    }
+  },
+  "resource_type": "OS::Heat::AResourceName"
+}`
+
+// HandleGetSchemaSuccessfully creates an HTTP handler at `/resource_types/OS::Heat::AResourceName`
+// on the test handler mux that responds with a `Schema` response.
+func HandleGetSchemaSuccessfully(t *testing.T, output string) {
+	th.Mux.HandleFunc("/resource_types/OS::Heat::AResourceName", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+
+		w.Header().Set("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprintf(w, output)
+	})
+}
+
+// GetTemplateExpected represents the expected object from a Template request.
+var GetTemplateExpected = &TypeTemplate{
+	HeatTemplateFormatVersion: "2012-12-12",
+	Outputs: map[string]interface{}{
+		"private_key": map[string]interface{}{
+			"Description": "The private key if it has been saved.",
+			"Value":       "{\"Fn::GetAtt\": [\"KeyPair\", \"private_key\"]}",
+		},
+		"public_key": map[string]interface{}{
+			"Description": "The public key.",
+			"Value":       "{\"Fn::GetAtt\": [\"KeyPair\", \"public_key\"]}",
+		},
+	},
+	Parameters: map[string]interface{}{
+		"name": map[string]interface{}{
+			"Description": "The name of the key pair.",
+			"Type":        "String",
+		},
+		"public_key": map[string]interface{}{
+			"Description": "The optional public key. This allows users to supply the public key from a pre-existing key pair. If not supplied, a new key pair will be generated.",
+			"Type":        "String",
+		},
+		"save_private_key": map[string]interface{}{
+			"AllowedValues": []string{
+				"True",
+				"true",
+				"False",
+				"false",
+			},
+			"Default":     false,
+			"Description": "True if the system should remember a generated private key; False otherwise.",
+			"Type":        "String",
+		},
+	},
+	Resources: map[string]interface{}{
+		"KeyPair": map[string]interface{}{
+			"Properties": map[string]interface{}{
+				"name": map[string]interface{}{
+					"Ref": "name",
+				},
+				"public_key": map[string]interface{}{
+					"Ref": "public_key",
+				},
+				"save_private_key": map[string]interface{}{
+					"Ref": "save_private_key",
+				},
+			},
+			"Type": "OS::Nova::KeyPair",
+		},
+	},
+}
+
+// GetTemplateOutput represents the response body from a Template request.
+const GetTemplateOutput = `
+{
+  "HeatTemplateFormatVersion": "2012-12-12",
+  "Outputs": {
+    "private_key": {
+      "Description": "The private key if it has been saved.",
+      "Value": "{\"Fn::GetAtt\": [\"KeyPair\", \"private_key\"]}"
+    },
+    "public_key": {
+      "Description": "The public key.",
+      "Value": "{\"Fn::GetAtt\": [\"KeyPair\", \"public_key\"]}"
+    }
+  },
+  "Parameters": {
+    "name": {
+      "Description": "The name of the key pair.",
+      "Type": "String"
+    },
+    "public_key": {
+      "Description": "The optional public key. This allows users to supply the public key from a pre-existing key pair. If not supplied, a new key pair will be generated.",
+      "Type": "String"
+    },
+    "save_private_key": {
+      "AllowedValues": [
+      "True",
+      "true",
+      "False",
+      "false"
+      ],
+      "Default": false,
+      "Description": "True if the system should remember a generated private key; False otherwise.",
+      "Type": "String"
+    }
+  },
+  "Resources": {
+    "KeyPair": {
+      "Properties": {
+        "name": {
+          "Ref": "name"
+        },
+        "public_key": {
+          "Ref": "public_key"
+        },
+        "save_private_key": {
+          "Ref": "save_private_key"
+        }
+      },
+      "Type": "OS::Nova::KeyPair"
+    }
+  }
+}`
+
+// HandleGetTemplateSuccessfully creates an HTTP handler at `/resource_types/OS::Heat::AResourceName/template`
+// on the test handler mux that responds with a `Template` response.
+func HandleGetTemplateSuccessfully(t *testing.T, output string) {
+	th.Mux.HandleFunc("/resource_types/OS::Heat::AResourceName/template", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+
+		w.Header().Set("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprintf(w, output)
+	})
+}
diff --git a/openstack/orchestration/v1/stackresources/requests.go b/openstack/orchestration/v1/stackresources/requests.go
new file mode 100644
index 0000000..0ca87b8
--- /dev/null
+++ b/openstack/orchestration/v1/stackresources/requests.go
@@ -0,0 +1,132 @@
+package stackresources
+
+import (
+	"github.com/racker/perigee"
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/pagination"
+)
+
+// Find retrieves stack resources for the given stack name.
+func Find(c *gophercloud.ServiceClient, stackName string) FindResult {
+	var res FindResult
+
+	// Send request to API
+	_, res.Err = perigee.Request("GET", findURL(c, stackName), perigee.Options{
+		MoreHeaders: c.AuthenticatedHeaders(),
+		Results:     &res.Body,
+		OkCodes:     []int{200},
+	})
+	return res
+}
+
+// ListOptsBuilder allows extensions to add additional parameters to the
+// List request.
+type ListOptsBuilder interface {
+	ToStackResourceListQuery() (string, error)
+}
+
+// ListOpts allows the filtering and sorting of paginated collections through
+// the API. Marker and Limit are used for pagination.
+type ListOpts struct {
+	// The stack resource ID with which to start the listing.
+	Marker string `q:"marker"`
+
+	// Integer value for the limit of values to return.
+	Limit int `q:"limit"`
+
+	// Include resources from nest stacks up to Depth levels of recursion.
+	Depth int `q:"nested_depth"`
+}
+
+// ToStackResourceListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToStackResourceListQuery() (string, error) {
+	q, err := gophercloud.BuildQueryString(opts)
+	if err != nil {
+		return "", err
+	}
+	return q.String(), nil
+}
+
+// List makes a request against the API to list resources for the given stack.
+func List(client *gophercloud.ServiceClient, stackName, stackID string, opts ListOptsBuilder) pagination.Pager {
+	url := listURL(client, stackName, stackID)
+
+	if opts != nil {
+		query, err := opts.ToStackResourceListQuery()
+		if err != nil {
+			return pagination.Pager{Err: err}
+		}
+		url += query
+	}
+
+	createPageFn := func(r pagination.PageResult) pagination.Page {
+		p := ResourcePage{pagination.MarkerPageBase{PageResult: r}}
+		p.MarkerPageBase.Owner = p
+		return p
+	}
+
+	return pagination.NewPager(client, url, createPageFn)
+}
+
+// Get retreives data for the given stack resource.
+func Get(c *gophercloud.ServiceClient, stackName, stackID, resourceName string) GetResult {
+	var res GetResult
+
+	// Send request to API
+	_, res.Err = perigee.Request("GET", getURL(c, stackName, stackID, resourceName), perigee.Options{
+		MoreHeaders: c.AuthenticatedHeaders(),
+		Results:     &res.Body,
+		OkCodes:     []int{200},
+	})
+	return res
+}
+
+// Metadata retreives the metadata for the given stack resource.
+func Metadata(c *gophercloud.ServiceClient, stackName, stackID, resourceName string) MetadataResult {
+	var res MetadataResult
+
+	// Send request to API
+	_, res.Err = perigee.Request("GET", metadataURL(c, stackName, stackID, resourceName), perigee.Options{
+		MoreHeaders: c.AuthenticatedHeaders(),
+		Results:     &res.Body,
+		OkCodes:     []int{200},
+	})
+	return res
+}
+
+// ListTypes makes a request against the API to list resource types.
+func ListTypes(client *gophercloud.ServiceClient) pagination.Pager {
+	url := listTypesURL(client)
+
+	createPageFn := func(r pagination.PageResult) pagination.Page {
+		return ResourceTypePage{pagination.SinglePageBase(r)}
+	}
+
+	return pagination.NewPager(client, url, createPageFn)
+}
+
+// Schema retreives the schema for the given resource type.
+func Schema(c *gophercloud.ServiceClient, resourceType string) SchemaResult {
+	var res SchemaResult
+
+	// Send request to API
+	_, res.Err = perigee.Request("GET", schemaURL(c, resourceType), perigee.Options{
+		MoreHeaders: c.AuthenticatedHeaders(),
+		Results:     &res.Body,
+		OkCodes:     []int{200},
+	})
+	return res
+}
+
+// Template retreives the template representation for the given resource type.
+func Template(c *gophercloud.ServiceClient, resourceType string) TemplateResult {
+	var res TemplateResult
+
+	// Send request to API
+	_, res.Err = perigee.Request("GET", templateURL(c, resourceType), perigee.Options{
+		MoreHeaders: c.AuthenticatedHeaders(),
+		Results:     &res.Body,
+		OkCodes:     []int{200},
+	})
+	return res
+}
diff --git a/openstack/orchestration/v1/stackresources/requests_test.go b/openstack/orchestration/v1/stackresources/requests_test.go
new file mode 100644
index 0000000..f137878
--- /dev/null
+++ b/openstack/orchestration/v1/stackresources/requests_test.go
@@ -0,0 +1,107 @@
+package stackresources
+
+import (
+	"testing"
+
+	"github.com/rackspace/gophercloud/pagination"
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func TestFindResources(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleFindSuccessfully(t, FindOutput)
+
+	actual, err := Find(fake.ServiceClient(), "hello_world").Extract()
+	th.AssertNoErr(t, err)
+
+	expected := FindExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
+
+func TestListResources(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleListSuccessfully(t, ListOutput)
+
+	count := 0
+	err := List(fake.ServiceClient(), "hello_world", "49181cd6-169a-4130-9455-31185bbfc5bf", nil).EachPage(func(page pagination.Page) (bool, error) {
+		count++
+		actual, err := ExtractResources(page)
+		th.AssertNoErr(t, err)
+
+		th.CheckDeepEquals(t, ListExpected, actual)
+
+		return true, nil
+	})
+	th.AssertNoErr(t, err)
+	th.CheckEquals(t, count, 1)
+}
+
+func TestGetResource(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleGetSuccessfully(t, GetOutput)
+
+	actual, err := Get(fake.ServiceClient(), "teststack", "0b1771bd-9336-4f2b-ae86-a80f971faf1e", "wordpress_instance").Extract()
+	th.AssertNoErr(t, err)
+
+	expected := GetExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
+
+func TestResourceMetadata(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleMetadataSuccessfully(t, MetadataOutput)
+
+	actual, err := Metadata(fake.ServiceClient(), "teststack", "0b1771bd-9336-4f2b-ae86-a80f971faf1e", "wordpress_instance").Extract()
+	th.AssertNoErr(t, err)
+
+	expected := MetadataExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
+
+func TestListResourceTypes(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleListTypesSuccessfully(t, ListTypesOutput)
+
+	count := 0
+	err := ListTypes(fake.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) {
+		count++
+		actual, err := ExtractResourceTypes(page)
+		th.AssertNoErr(t, err)
+
+		th.CheckDeepEquals(t, ListTypesExpected, actual)
+
+		return true, nil
+	})
+	th.AssertNoErr(t, err)
+	th.CheckEquals(t, 1, count)
+}
+
+func TestGetResourceSchema(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleGetSchemaSuccessfully(t, GetSchemaOutput)
+
+	actual, err := Schema(fake.ServiceClient(), "OS::Heat::AResourceName").Extract()
+	th.AssertNoErr(t, err)
+
+	expected := GetSchemaExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
+
+func TestGetResourceTemplate(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleGetTemplateSuccessfully(t, GetTemplateOutput)
+
+	actual, err := Template(fake.ServiceClient(), "OS::Heat::AResourceName").Extract()
+	th.AssertNoErr(t, err)
+
+	expected := GetTemplateExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
diff --git a/openstack/orchestration/v1/stackresources/results.go b/openstack/orchestration/v1/stackresources/results.go
new file mode 100644
index 0000000..13f5dd2
--- /dev/null
+++ b/openstack/orchestration/v1/stackresources/results.go
@@ -0,0 +1,250 @@
+package stackresources
+
+import (
+	"time"
+
+	"github.com/mitchellh/mapstructure"
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/pagination"
+)
+
+// Resource represents a stack resource.
+type Resource struct {
+	Links        []gophercloud.Link `mapstructure:"links"`
+	LogicalID    string             `mapstructure:"logical_resource_id"`
+	Name         string             `mapstructure:"resource_name"`
+	PhysicalID   string             `mapstructure:"physical_resource_id"`
+	RequiredBy   []interface{}      `mapstructure:"required_by"`
+	Status       string             `mapstructure:"resource_status"`
+	StatusReason string             `mapstructure:"resource_status_reason"`
+	Type         string             `mapstructure:"resource_type"`
+	UpdatedTime  time.Time          `mapstructure:"-"`
+}
+
+// FindResult represents the result of a Find operation.
+type FindResult struct {
+	gophercloud.Result
+}
+
+// Extract returns a slice of Resource objects and is called after a
+// Find operation.
+func (r FindResult) Extract() ([]Resource, error) {
+	if r.Err != nil {
+		return nil, r.Err
+	}
+
+	var res struct {
+		Res []Resource `mapstructure:"resources"`
+	}
+
+	if err := mapstructure.Decode(r.Body, &res); err != nil {
+		return nil, err
+	}
+
+	resources := r.Body.(map[string]interface{})["resources"].([]interface{})
+
+	for i, resourceRaw := range resources {
+		resource := resourceRaw.(map[string]interface{})
+		if date, ok := resource["updated_time"]; ok && date != nil {
+			t, err := time.Parse(time.RFC3339, date.(string))
+			if err != nil {
+				return nil, err
+			}
+			res.Res[i].UpdatedTime = t
+		}
+	}
+
+	return res.Res, nil
+}
+
+// ResourcePage abstracts the raw results of making a List() request against the API.
+// As OpenStack extensions may freely alter the response bodies of structures returned to the client, you may only safely access the
+// data provided through the ExtractResources call.
+type ResourcePage struct {
+	pagination.MarkerPageBase
+}
+
+// IsEmpty returns true if a page contains no Server results.
+func (r ResourcePage) IsEmpty() (bool, error) {
+	resources, err := ExtractResources(r)
+	if err != nil {
+		return true, err
+	}
+	return len(resources) == 0, nil
+}
+
+// LastMarker returns the last container name in a ListResult.
+func (r ResourcePage) LastMarker() (string, error) {
+	resources, err := ExtractResources(r)
+	if err != nil {
+		return "", err
+	}
+	if len(resources) == 0 {
+		return "", nil
+	}
+	return resources[len(resources)-1].PhysicalID, nil
+}
+
+// ExtractResources interprets the results of a single page from a List() call, producing a slice of Resource entities.
+func ExtractResources(page pagination.Page) ([]Resource, error) {
+	casted := page.(ResourcePage).Body
+
+	var response struct {
+		Resources []Resource `mapstructure:"resources"`
+	}
+	err := mapstructure.Decode(casted, &response)
+
+	resources := casted.(map[string]interface{})["resources"].([]interface{})
+
+	for i, resourceRaw := range resources {
+		resource := resourceRaw.(map[string]interface{})
+		if date, ok := resource["updated_time"]; ok && date != nil {
+			t, err := time.Parse(time.RFC3339, date.(string))
+			if err != nil {
+				return nil, err
+			}
+			response.Resources[i].UpdatedTime = t
+		}
+	}
+
+	return response.Resources, err
+}
+
+// GetResult represents the result of a Get operation.
+type GetResult struct {
+	gophercloud.Result
+}
+
+// Extract returns a pointer to a Resource object and is called after a
+// Get operation.
+func (r GetResult) Extract() (*Resource, error) {
+	if r.Err != nil {
+		return nil, r.Err
+	}
+
+	var res struct {
+		Res *Resource `mapstructure:"resource"`
+	}
+
+	if err := mapstructure.Decode(r.Body, &res); err != nil {
+		return nil, err
+	}
+
+	resource := r.Body.(map[string]interface{})["resource"].(map[string]interface{})
+
+	if date, ok := resource["updated_time"]; ok && date != nil {
+		t, err := time.Parse(time.RFC3339, date.(string))
+		if err != nil {
+			return nil, err
+		}
+		res.Res.UpdatedTime = t
+	}
+
+	return res.Res, nil
+}
+
+// MetadataResult represents the result of a Metadata operation.
+type MetadataResult struct {
+	gophercloud.Result
+}
+
+// Extract returns a map object and is called after a
+// Metadata operation.
+func (r MetadataResult) Extract() (map[string]string, error) {
+	if r.Err != nil {
+		return nil, r.Err
+	}
+
+	var res struct {
+		Meta map[string]string `mapstructure:"metadata"`
+	}
+
+	if err := mapstructure.Decode(r.Body, &res); err != nil {
+		return nil, err
+	}
+
+	return res.Meta, nil
+}
+
+// ResourceTypePage abstracts the raw results of making a ListTypes() request against the API.
+// As OpenStack extensions may freely alter the response bodies of structures returned to the client, you may only safely access the
+// data provided through the ExtractResourceTypes call.
+type ResourceTypePage struct {
+	pagination.SinglePageBase
+}
+
+// IsEmpty returns true if a ResourceTypePage contains no resource types.
+func (r ResourceTypePage) IsEmpty() (bool, error) {
+	rts, err := ExtractResourceTypes(r)
+	if err != nil {
+		return true, err
+	}
+	return len(rts) == 0, nil
+}
+
+// ExtractResourceTypes extracts and returns resource types.
+func ExtractResourceTypes(page pagination.Page) ([]string, error) {
+	var response struct {
+		ResourceTypes []string `mapstructure:"resource_types"`
+	}
+
+	err := mapstructure.Decode(page.(ResourceTypePage).Body, &response)
+	return response.ResourceTypes, err
+}
+
+// TypeSchema represents a stack resource schema.
+type TypeSchema struct {
+	Attributes   map[string]interface{} `mapstructure:"attributes"`
+	Properties   map[string]interface{} `mapstrucutre:"properties"`
+	ResourceType string                 `mapstructure:"resource_type"`
+}
+
+// SchemaResult represents the result of a Schema operation.
+type SchemaResult struct {
+	gophercloud.Result
+}
+
+// Extract returns a pointer to a TypeSchema object and is called after a
+// Schema operation.
+func (r SchemaResult) Extract() (*TypeSchema, error) {
+	if r.Err != nil {
+		return nil, r.Err
+	}
+
+	var res TypeSchema
+
+	if err := mapstructure.Decode(r.Body, &res); err != nil {
+		return nil, err
+	}
+
+	return &res, nil
+}
+
+// TypeTemplate represents a stack resource template.
+type TypeTemplate struct {
+	HeatTemplateFormatVersion string
+	Outputs                   map[string]interface{}
+	Parameters                map[string]interface{}
+	Resources                 map[string]interface{}
+}
+
+// TemplateResult represents the result of a Template operation.
+type TemplateResult struct {
+	gophercloud.Result
+}
+
+// Extract returns a pointer to a TypeTemplate object and is called after a
+// Template operation.
+func (r TemplateResult) Extract() (*TypeTemplate, error) {
+	if r.Err != nil {
+		return nil, r.Err
+	}
+
+	var res TypeTemplate
+
+	if err := mapstructure.Decode(r.Body, &res); err != nil {
+		return nil, err
+	}
+
+	return &res, nil
+}
diff --git a/openstack/orchestration/v1/stackresources/urls.go b/openstack/orchestration/v1/stackresources/urls.go
new file mode 100644
index 0000000..ef078d9
--- /dev/null
+++ b/openstack/orchestration/v1/stackresources/urls.go
@@ -0,0 +1,31 @@
+package stackresources
+
+import "github.com/rackspace/gophercloud"
+
+func findURL(c *gophercloud.ServiceClient, stackName string) string {
+	return c.ServiceURL("stacks", stackName, "resources")
+}
+
+func listURL(c *gophercloud.ServiceClient, stackName, stackID string) string {
+	return c.ServiceURL("stacks", stackName, stackID, "resources")
+}
+
+func getURL(c *gophercloud.ServiceClient, stackName, stackID, resourceName string) string {
+	return c.ServiceURL("stacks", stackName, stackID, "resources", resourceName)
+}
+
+func metadataURL(c *gophercloud.ServiceClient, stackName, stackID, resourceName string) string {
+	return c.ServiceURL("stacks", stackName, stackID, "resources", resourceName, "metadata")
+}
+
+func listTypesURL(c *gophercloud.ServiceClient) string {
+	return c.ServiceURL("resource_types")
+}
+
+func schemaURL(c *gophercloud.ServiceClient, typeName string) string {
+	return c.ServiceURL("resource_types", typeName)
+}
+
+func templateURL(c *gophercloud.ServiceClient, typeName string) string {
+	return c.ServiceURL("resource_types", typeName, "template")
+}
diff --git a/openstack/orchestration/v1/stacks/doc.go b/openstack/orchestration/v1/stacks/doc.go
new file mode 100644
index 0000000..19231b5
--- /dev/null
+++ b/openstack/orchestration/v1/stacks/doc.go
@@ -0,0 +1,8 @@
+// Package stacks provides operation for working with Heat stacks. A stack is a
+// group of resources (servers, load balancers, databases, and so forth)
+// combined to fulfill a useful purpose. Based on a template, Heat orchestration
+// engine creates an instantiated set of resources (a stack) to run the
+// application framework or component specified (in the template). A stack is a
+// running instance of a template. The result of creating a stack is a deployment
+// of the application framework or component.
+package stacks
diff --git a/openstack/orchestration/v1/stacks/fixtures.go b/openstack/orchestration/v1/stacks/fixtures.go
new file mode 100644
index 0000000..6d3e959
--- /dev/null
+++ b/openstack/orchestration/v1/stacks/fixtures.go
@@ -0,0 +1,374 @@
+package stacks
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+	"time"
+
+	"github.com/rackspace/gophercloud"
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+// CreateExpected represents the expected object from a Create request.
+var CreateExpected = &CreatedStack{
+	ID: "16ef0584-4458-41eb-87c8-0dc8d5f66c87",
+	Links: []gophercloud.Link{
+		gophercloud.Link{
+			Href: "http://168.28.170.117:8004/v1/98606384f58drad0bhdb7d02779549ac/stacks/stackcreated/16ef0584-4458-41eb-87c8-0dc8d5f66c87",
+			Rel:  "self",
+		},
+	},
+}
+
+// CreateOutput represents the response body from a Create request.
+const CreateOutput = `
+{
+  "stack": {
+    "id": "16ef0584-4458-41eb-87c8-0dc8d5f66c87",
+    "links": [
+    {
+      "href": "http://168.28.170.117:8004/v1/98606384f58drad0bhdb7d02779549ac/stacks/stackcreated/16ef0584-4458-41eb-87c8-0dc8d5f66c87",
+      "rel": "self"
+    }
+    ]
+  }
+}`
+
+// HandleCreateSuccessfully creates an HTTP handler at `/stacks` on the test handler mux
+// that responds with a `Create` response.
+func HandleCreateSuccessfully(t *testing.T, output string) {
+	th.Mux.HandleFunc("/stacks", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "POST")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+		w.WriteHeader(http.StatusCreated)
+		fmt.Fprintf(w, output)
+	})
+}
+
+// ListExpected represents the expected object from a List request.
+var ListExpected = []ListedStack{
+	ListedStack{
+		Description: "Simple template to test heat commands",
+		Links: []gophercloud.Link{
+			gophercloud.Link{
+				Href: "http://166.76.160.117:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87",
+				Rel:  "self",
+			},
+		},
+		StatusReason: "Stack CREATE completed successfully",
+		Name:         "postman_stack",
+		CreationTime: time.Date(2015, 2, 3, 20, 7, 39, 0, time.UTC),
+		Status:       "CREATE_COMPLETE",
+		ID:           "16ef0584-4458-41eb-87c8-0dc8d5f66c87",
+	},
+	ListedStack{
+		Description: "Simple template to test heat commands",
+		Links: []gophercloud.Link{
+			gophercloud.Link{
+				Href: "http://166.76.160.117:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/gophercloud-test-stack-2/db6977b2-27aa-4775-9ae7-6213212d4ada",
+				Rel:  "self",
+			},
+		},
+		StatusReason: "Stack successfully updated",
+		Name:         "gophercloud-test-stack-2",
+		CreationTime: time.Date(2014, 12, 11, 17, 39, 16, 0, time.UTC),
+		UpdatedTime:  time.Date(2014, 12, 11, 17, 40, 37, 0, time.UTC),
+		Status:       "UPDATE_COMPLETE",
+		ID:           "db6977b2-27aa-4775-9ae7-6213212d4ada",
+	},
+}
+
+// FullListOutput represents the response body from a List request without a marker.
+const FullListOutput = `
+{
+  "stacks": [
+  {
+    "description": "Simple template to test heat commands",
+    "links": [
+    {
+      "href": "http://166.76.160.117:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87",
+      "rel": "self"
+    }
+    ],
+    "stack_status_reason": "Stack CREATE completed successfully",
+    "stack_name": "postman_stack",
+    "creation_time": "2015-02-03T20:07:39Z",
+    "updated_time": null,
+    "stack_status": "CREATE_COMPLETE",
+    "id": "16ef0584-4458-41eb-87c8-0dc8d5f66c87"
+  },
+  {
+    "description": "Simple template to test heat commands",
+    "links": [
+    {
+      "href": "http://166.76.160.117:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/gophercloud-test-stack-2/db6977b2-27aa-4775-9ae7-6213212d4ada",
+      "rel": "self"
+    }
+    ],
+    "stack_status_reason": "Stack successfully updated",
+    "stack_name": "gophercloud-test-stack-2",
+    "creation_time": "2014-12-11T17:39:16Z",
+    "updated_time": "2014-12-11T17:40:37Z",
+    "stack_status": "UPDATE_COMPLETE",
+    "id": "db6977b2-27aa-4775-9ae7-6213212d4ada"
+  }
+  ]
+}
+`
+
+// HandleListSuccessfully creates an HTTP handler at `/stacks` on the test handler mux
+// that responds with a `List` response.
+func HandleListSuccessfully(t *testing.T, output string) {
+	th.Mux.HandleFunc("/stacks", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+
+		w.Header().Set("Content-Type", "application/json")
+		r.ParseForm()
+		marker := r.Form.Get("marker")
+		switch marker {
+		case "":
+			fmt.Fprintf(w, output)
+		case "db6977b2-27aa-4775-9ae7-6213212d4ada":
+			fmt.Fprintf(w, `[]`)
+		default:
+			t.Fatalf("Unexpected marker: [%s]", marker)
+		}
+	})
+}
+
+// GetExpected represents the expected object from a Get request.
+var GetExpected = &RetrievedStack{
+	DisableRollback: true,
+	Description:     "Simple template to test heat commands",
+	Parameters: map[string]string{
+		"flavor":         "m1.tiny",
+		"OS::stack_name": "postman_stack",
+		"OS::stack_id":   "16ef0584-4458-41eb-87c8-0dc8d5f66c87",
+	},
+	StatusReason: "Stack CREATE completed successfully",
+	Name:         "postman_stack",
+	Outputs:      []map[string]interface{}{},
+	CreationTime: time.Date(2015, 2, 3, 20, 7, 39, 0, time.UTC),
+	Links: []gophercloud.Link{
+		gophercloud.Link{
+			Href: "http://166.76.160.117:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87",
+			Rel:  "self",
+		},
+	},
+	Capabilities:        []interface{}{},
+	NotificationTopics:  []interface{}{},
+	Status:              "CREATE_COMPLETE",
+	ID:                  "16ef0584-4458-41eb-87c8-0dc8d5f66c87",
+	TemplateDescription: "Simple template to test heat commands",
+}
+
+// GetOutput represents the response body from a Get request.
+const GetOutput = `
+{
+  "stack": {
+    "disable_rollback": true,
+    "description": "Simple template to test heat commands",
+    "parameters": {
+      "flavor": "m1.tiny",
+      "OS::stack_name": "postman_stack",
+      "OS::stack_id": "16ef0584-4458-41eb-87c8-0dc8d5f66c87"
+    },
+    "stack_status_reason": "Stack CREATE completed successfully",
+    "stack_name": "postman_stack",
+    "outputs": [],
+    "creation_time": "2015-02-03T20:07:39Z",
+    "links": [
+    {
+      "href": "http://166.76.160.117:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87",
+      "rel": "self"
+    }
+    ],
+    "capabilities": [],
+    "notification_topics": [],
+    "timeout_mins": null,
+    "stack_status": "CREATE_COMPLETE",
+    "updated_time": null,
+    "id": "16ef0584-4458-41eb-87c8-0dc8d5f66c87",
+    "template_description": "Simple template to test heat commands"
+  }
+}
+`
+
+// HandleGetSuccessfully creates an HTTP handler at `/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87`
+// on the test handler mux that responds with a `Get` response.
+func HandleGetSuccessfully(t *testing.T, output string) {
+	th.Mux.HandleFunc("/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+
+		w.Header().Set("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprintf(w, output)
+	})
+}
+
+// HandleUpdateSuccessfully creates an HTTP handler at `/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87`
+// on the test handler mux that responds with an `Update` response.
+func HandleUpdateSuccessfully(t *testing.T) {
+	th.Mux.HandleFunc("/stacks/gophercloud-test-stack-2/db6977b2-27aa-4775-9ae7-6213212d4ada", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "PUT")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+
+		w.Header().Set("Content-Type", "application/json")
+		w.WriteHeader(http.StatusAccepted)
+	})
+}
+
+// HandleDeleteSuccessfully creates an HTTP handler at `/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87`
+// on the test handler mux that responds with a `Delete` response.
+func HandleDeleteSuccessfully(t *testing.T) {
+	th.Mux.HandleFunc("/stacks/gophercloud-test-stack-2/db6977b2-27aa-4775-9ae7-6213212d4ada", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "DELETE")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+
+		w.Header().Set("Content-Type", "application/json")
+		w.WriteHeader(http.StatusNoContent)
+	})
+}
+
+// GetExpected represents the expected object from a Get request.
+var PreviewExpected = &PreviewedStack{
+	DisableRollback: true,
+	Description:     "Simple template to test heat commands",
+	Parameters: map[string]string{
+		"flavor":         "m1.tiny",
+		"OS::stack_name": "postman_stack",
+		"OS::stack_id":   "16ef0584-4458-41eb-87c8-0dc8d5f66c87",
+	},
+	StatusReason: "Stack CREATE completed successfully",
+	Name:         "postman_stack",
+	CreationTime: time.Date(2015, 2, 3, 20, 7, 39, 0, time.UTC),
+	Links: []gophercloud.Link{
+		gophercloud.Link{
+			Href: "http://166.76.160.117:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87",
+			Rel:  "self",
+		},
+	},
+	Capabilities:        []interface{}{},
+	NotificationTopics:  []interface{}{},
+	Status:              "CREATE_COMPLETE",
+	ID:                  "16ef0584-4458-41eb-87c8-0dc8d5f66c87",
+	TemplateDescription: "Simple template to test heat commands",
+}
+
+// HandlePreviewSuccessfully creates an HTTP handler at `/stacks/preview`
+// on the test handler mux that responds with a `Preview` response.
+func HandlePreviewSuccessfully(t *testing.T, output string) {
+	th.Mux.HandleFunc("/stacks/preview", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "POST")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+
+		w.Header().Set("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprintf(w, output)
+	})
+}
+
+// AbandonExpected represents the expected object from an Abandon request.
+var AbandonExpected = &AbandonedStack{
+	Status: "COMPLETE",
+	Name:   "postman_stack",
+	Template: map[string]interface{}{
+		"heat_template_version": "2013-05-23",
+		"description":           "Simple template to test heat commands",
+		"parameters": map[string]interface{}{
+			"flavor": map[string]interface{}{
+				"default": "m1.tiny",
+				"type":    "string",
+			},
+		},
+		"resources": map[string]interface{}{
+			"hello_world": map[string]interface{}{
+				"type": "OS::Nova::Server",
+				"properties": map[string]interface{}{
+					"key_name": "heat_key",
+					"flavor": map[string]interface{}{
+						"get_param": "flavor",
+					},
+					"image":     "ad091b52-742f-469e-8f3c-fd81cadf0743",
+					"user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n",
+				},
+			},
+		},
+	},
+	Action: "CREATE",
+	ID:     "16ef0584-4458-41eb-87c8-0dc8d5f66c87",
+	Resources: map[string]interface{}{
+		"hello_world": map[string]interface{}{
+			"status":      "COMPLETE",
+			"name":        "hello_world",
+			"resource_id": "8a310d36-46fc-436f-8be4-37a696b8ac63",
+			"action":      "CREATE",
+			"type":        "OS::Nova::Server",
+		},
+	},
+}
+
+// AbandonOutput represents the response body from an Abandon request.
+const AbandonOutput = `
+{
+  "status": "COMPLETE",
+  "name": "postman_stack",
+  "template": {
+    "heat_template_version": "2013-05-23",
+    "description": "Simple template to test heat commands",
+    "parameters": {
+      "flavor": {
+        "default": "m1.tiny",
+        "type": "string"
+      }
+    },
+    "resources": {
+      "hello_world": {
+        "type": "OS::Nova::Server",
+        "properties": {
+          "key_name": "heat_key",
+          "flavor": {
+            "get_param": "flavor"
+          },
+          "image": "ad091b52-742f-469e-8f3c-fd81cadf0743",
+          "user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n"
+        }
+      }
+    }
+  },
+  "action": "CREATE",
+  "id": "16ef0584-4458-41eb-87c8-0dc8d5f66c87",
+  "resources": {
+    "hello_world": {
+      "status": "COMPLETE",
+      "name": "hello_world",
+      "resource_id": "8a310d36-46fc-436f-8be4-37a696b8ac63",
+      "action": "CREATE",
+      "type": "OS::Nova::Server",
+    }
+  }
+}`
+
+// HandleAbandonSuccessfully creates an HTTP handler at `/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87/abandon`
+// on the test handler mux that responds with an `Abandon` response.
+func HandleAbandonSuccessfully(t *testing.T) {
+	th.Mux.HandleFunc("/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87/abandon", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "DELETE")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+
+		w.Header().Set("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprintf(w, AbandonOutput)
+	})
+}
diff --git a/openstack/orchestration/v1/stacks/requests.go b/openstack/orchestration/v1/stacks/requests.go
new file mode 100644
index 0000000..549aecf
--- /dev/null
+++ b/openstack/orchestration/v1/stacks/requests.go
@@ -0,0 +1,528 @@
+package stacks
+
+import (
+	"errors"
+
+	"github.com/racker/perigee"
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/pagination"
+)
+
+// Rollback is used to specify whether or not a stack can be rolled back.
+type Rollback *bool
+
+var (
+	disable = true
+	// Disable is used to specify that a stack cannot be rolled back.
+	Disable Rollback = &disable
+	enable           = false
+	// Enable is used to specify that a stack can be rolled back.
+	Enable Rollback = &enable
+)
+
+// CreateOptsBuilder is the interface options structs have to satisfy in order
+// to be used in the main Create operation in this package. Since many
+// extensions decorate or modify the common logic, it is useful for them to
+// satisfy a basic interface in order for them to be used.
+type CreateOptsBuilder interface {
+	ToStackCreateMap() (map[string]interface{}, error)
+}
+
+// CreateOpts is the common options struct used in this package's Create
+// operation.
+type CreateOpts struct {
+	// (REQUIRED) The name of the stack. It must start with an alphabetic character.
+	Name string
+	// (OPTIONAL; REQUIRED IF Template IS EMPTY) The URL of the template to instantiate.
+	// This value is ignored if Template is supplied inline.
+	TemplateURL string
+	// (OPTIONAL; REQUIRED IF TemplateURL IS EMPTY) A template to instantiate. The value
+	// is a stringified version of the JSON/YAML template. Since the template will likely
+	// be located in a file, one way to set this variable is by using ioutil.ReadFile:
+	// import "io/ioutil"
+	// var opts stacks.CreateOpts
+	// b, err := ioutil.ReadFile("path/to/you/template/file.json")
+	// if err != nil {
+	//   // handle error...
+	// }
+	// opts.Template = string(b)
+	Template string
+	// (OPTIONAL) Enables or disables deletion of all stack resources when a stack
+	// creation fails. Default is true, meaning all resources are not deleted when
+	// stack creation fails.
+	DisableRollback Rollback
+	// (OPTIONAL) A stringified JSON environment for the stack.
+	Environment string
+	// (OPTIONAL) A map that maps file names to file contents. It can also be used
+	// to pass provider template contents. Example:
+	// Files: `{"myfile": "#!/bin/bash\necho 'Hello world' > /root/testfile.txt"}`
+	Files map[string]interface{}
+	// (OPTIONAL) User-defined parameters to pass to the template.
+	Parameters map[string]string
+	// (OPTIONAL) The timeout for stack creation in minutes.
+	Timeout int
+}
+
+// ToStackCreateMap casts a CreateOpts struct to a map.
+func (opts CreateOpts) ToStackCreateMap() (map[string]interface{}, error) {
+	s := make(map[string]interface{})
+
+	if opts.Name == "" {
+		return s, errors.New("Required field 'Name' not provided.")
+	}
+	s["stack_name"] = opts.Name
+
+	if opts.Template != "" {
+		s["template"] = opts.Template
+	} else if opts.TemplateURL != "" {
+		s["template_url"] = opts.TemplateURL
+	} else {
+		return s, errors.New("Either Template or TemplateURL must be provided.")
+	}
+
+	if opts.DisableRollback != nil {
+		s["disable_rollback"] = &opts.DisableRollback
+	}
+
+	if opts.Environment != "" {
+		s["environment"] = opts.Environment
+	}
+	if opts.Files != nil {
+		s["files"] = opts.Files
+	}
+	if opts.Parameters != nil {
+		s["parameters"] = opts.Parameters
+	}
+
+	if opts.Timeout != 0 {
+		s["timeout_mins"] = opts.Timeout
+	}
+
+	return s, nil
+}
+
+// Create accepts a CreateOpts struct and creates a new stack using the values
+// provided.
+func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
+	var res CreateResult
+
+	reqBody, err := opts.ToStackCreateMap()
+	if err != nil {
+		res.Err = err
+		return res
+	}
+
+	// Send request to API
+	_, res.Err = perigee.Request("POST", createURL(c), perigee.Options{
+		MoreHeaders: c.AuthenticatedHeaders(),
+		ReqBody:     &reqBody,
+		Results:     &res.Body,
+		OkCodes:     []int{201},
+	})
+	return res
+}
+
+// AdoptOptsBuilder is the interface options structs have to satisfy in order
+// to be used in the Adopt function in this package. Since many
+// extensions decorate or modify the common logic, it is useful for them to
+// satisfy a basic interface in order for them to be used.
+type AdoptOptsBuilder interface {
+	ToStackAdoptMap() (map[string]interface{}, error)
+}
+
+// AdoptOpts is the common options struct used in this package's Adopt
+// operation.
+type AdoptOpts struct {
+	// (REQUIRED) Existing resources data represented as a string to add to the
+	// new stack. Data returned by Abandon could be provided as AdoptsStackData.
+	AdoptStackData string
+	// (REQUIRED) The name of the stack. It must start with an alphabetic character.
+	Name string
+	// (REQUIRED) The timeout for stack creation in minutes.
+	Timeout int
+	// (OPTIONAL; REQUIRED IF Template IS EMPTY) The URL of the template to instantiate.
+	// This value is ignored if Template is supplied inline.
+	TemplateURL string
+	// (OPTIONAL; REQUIRED IF TemplateURL IS EMPTY) A template to instantiate. The value
+	// is a stringified version of the JSON/YAML template. Since the template will likely
+	// be located in a file, one way to set this variable is by using ioutil.ReadFile:
+	// import "io/ioutil"
+	// var opts stacks.CreateOpts
+	// b, err := ioutil.ReadFile("path/to/you/template/file.json")
+	// if err != nil {
+	//   // handle error...
+	// }
+	// opts.Template = string(b)
+	Template string
+	// (OPTIONAL) Enables or disables deletion of all stack resources when a stack
+	// creation fails. Default is true, meaning all resources are not deleted when
+	// stack creation fails.
+	DisableRollback Rollback
+	// (OPTIONAL) A stringified JSON environment for the stack.
+	Environment string
+	// (OPTIONAL) A map that maps file names to file contents. It can also be used
+	// to pass provider template contents. Example:
+	// Files: `{"myfile": "#!/bin/bash\necho 'Hello world' > /root/testfile.txt"}`
+	Files map[string]interface{}
+	// (OPTIONAL) User-defined parameters to pass to the template.
+	Parameters map[string]string
+}
+
+// ToStackAdoptMap casts a CreateOpts struct to a map.
+func (opts AdoptOpts) ToStackAdoptMap() (map[string]interface{}, error) {
+	s := make(map[string]interface{})
+
+	if opts.Name == "" {
+		return s, errors.New("Required field 'Name' not provided.")
+	}
+	s["stack_name"] = opts.Name
+
+	if opts.Template != "" {
+		s["template"] = opts.Template
+	} else if opts.TemplateURL != "" {
+		s["template_url"] = opts.TemplateURL
+	} else {
+		return s, errors.New("Either Template or TemplateURL must be provided.")
+	}
+
+	if opts.AdoptStackData == "" {
+		return s, errors.New("Required field 'AdoptStackData' not provided.")
+	}
+	s["adopt_stack_data"] = opts.AdoptStackData
+
+	if opts.DisableRollback != nil {
+		s["disable_rollback"] = &opts.DisableRollback
+	}
+
+	if opts.Environment != "" {
+		s["environment"] = opts.Environment
+	}
+	if opts.Files != nil {
+		s["files"] = opts.Files
+	}
+	if opts.Parameters != nil {
+		s["parameters"] = opts.Parameters
+	}
+
+	if opts.Timeout == 0 {
+		return nil, errors.New("Required field 'Timeout' not provided.")
+	}
+	s["timeout_mins"] = opts.Timeout
+
+	return map[string]interface{}{"stack": s}, nil
+}
+
+// Adopt accepts an AdoptOpts struct and creates a new stack using the resources
+// from another stack.
+func Adopt(c *gophercloud.ServiceClient, opts AdoptOptsBuilder) AdoptResult {
+	var res AdoptResult
+
+	reqBody, err := opts.ToStackAdoptMap()
+	if err != nil {
+		res.Err = err
+		return res
+	}
+
+	// Send request to API
+	_, res.Err = perigee.Request("POST", adoptURL(c), perigee.Options{
+		MoreHeaders: c.AuthenticatedHeaders(),
+		ReqBody:     &reqBody,
+		Results:     &res.Body,
+		OkCodes:     []int{201},
+	})
+	return res
+}
+
+// SortDir is a type for specifying in which direction to sort a list of stacks.
+type SortDir string
+
+// SortKey is a type for specifying by which key to sort a list of stacks.
+type SortKey string
+
+var (
+	// SortAsc is used to sort a list of stacks in ascending order.
+	SortAsc SortDir = "asc"
+	// SortDesc is used to sort a list of stacks in descending order.
+	SortDesc SortDir = "desc"
+	// SortName is used to sort a list of stacks by name.
+	SortName SortKey = "name"
+	// SortStatus is used to sort a list of stacks by status.
+	SortStatus SortKey = "status"
+	// SortCreatedAt is used to sort a list of stacks by date created.
+	SortCreatedAt SortKey = "created_at"
+	// SortUpdatedAt is used to sort a list of stacks by date updated.
+	SortUpdatedAt SortKey = "updated_at"
+)
+
+// ListOptsBuilder allows extensions to add additional parameters to the
+// List request.
+type ListOptsBuilder interface {
+	ToStackListQuery() (string, error)
+}
+
+// ListOpts allows the filtering and sorting of paginated collections through
+// the API. Filtering is achieved by passing in struct field values that map to
+// the network attributes you want to see returned. SortKey allows you to sort
+// by a particular network attribute. SortDir sets the direction, and is either
+// `asc' or `desc'. Marker and Limit are used for pagination.
+type ListOpts struct {
+	Status  string  `q:"status"`
+	Name    string  `q:"name"`
+	Marker  string  `q:"marker"`
+	Limit   int     `q:"limit"`
+	SortKey SortKey `q:"sort_keys"`
+	SortDir SortDir `q:"sort_dir"`
+}
+
+// ToStackListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToStackListQuery() (string, error) {
+	q, err := gophercloud.BuildQueryString(opts)
+	if err != nil {
+		return "", err
+	}
+	return q.String(), nil
+}
+
+// List returns a Pager which allows you to iterate over a collection of
+// stacks. It accepts a ListOpts struct, which allows you to filter and sort
+// the returned collection for greater efficiency.
+func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
+	url := listURL(c)
+	if opts != nil {
+		query, err := opts.ToStackListQuery()
+		if err != nil {
+			return pagination.Pager{Err: err}
+		}
+		url += query
+	}
+
+	createPage := func(r pagination.PageResult) pagination.Page {
+		return StackPage{pagination.SinglePageBase(r)}
+	}
+	return pagination.NewPager(c, url, createPage)
+}
+
+// Get retreives a stack based on the stack name and stack ID.
+func Get(c *gophercloud.ServiceClient, stackName, stackID string) GetResult {
+	var res GetResult
+
+	// Send request to API
+	_, res.Err = perigee.Request("GET", getURL(c, stackName, stackID), perigee.Options{
+		MoreHeaders: c.AuthenticatedHeaders(),
+		Results:     &res.Body,
+		OkCodes:     []int{200},
+	})
+	return res
+}
+
+// UpdateOptsBuilder is the interface options structs have to satisfy in order
+// to be used in the Update operation in this package.
+type UpdateOptsBuilder interface {
+	ToStackUpdateMap() (map[string]interface{}, error)
+}
+
+// UpdateOpts contains the common options struct used in this package's Update
+// operation.
+type UpdateOpts struct {
+	// (OPTIONAL; REQUIRED IF Template IS EMPTY) The URL of the template to instantiate.
+	// This value is ignored if Template is supplied inline.
+	TemplateURL string
+	// (OPTIONAL; REQUIRED IF TemplateURL IS EMPTY) A template to instantiate. The value
+	// is a stringified version of the JSON/YAML template. Since the template will likely
+	// be located in a file, one way to set this variable is by using ioutil.ReadFile:
+	// import "io/ioutil"
+	// var opts stacks.CreateOpts
+	// b, err := ioutil.ReadFile("path/to/you/template/file.json")
+	// if err != nil {
+	//   // handle error...
+	// }
+	// opts.Template = string(b)
+	Template string
+	// (OPTIONAL) A stringified JSON environment for the stack.
+	Environment string
+	// (OPTIONAL) A map that maps file names to file contents. It can also be used
+	// to pass provider template contents. Example:
+	// Files: `{"myfile": "#!/bin/bash\necho 'Hello world' > /root/testfile.txt"}`
+	Files map[string]interface{}
+	// (OPTIONAL) User-defined parameters to pass to the template.
+	Parameters map[string]string
+	// (OPTIONAL) The timeout for stack creation in minutes.
+	Timeout int
+}
+
+// ToStackUpdateMap casts a CreateOpts struct to a map.
+func (opts UpdateOpts) ToStackUpdateMap() (map[string]interface{}, error) {
+	s := make(map[string]interface{})
+
+	if opts.Template != "" {
+		s["template"] = opts.Template
+	} else if opts.TemplateURL != "" {
+		s["template_url"] = opts.TemplateURL
+	} else {
+		return s, errors.New("Either Template or TemplateURL must be provided.")
+	}
+
+	if opts.Environment != "" {
+		s["environment"] = opts.Environment
+	}
+
+	if opts.Files != nil {
+		s["files"] = opts.Files
+	}
+
+	if opts.Parameters != nil {
+		s["parameters"] = opts.Parameters
+	}
+
+	if opts.Timeout != 0 {
+		s["timeout_mins"] = opts.Timeout
+	}
+
+	return s, nil
+}
+
+// Update accepts an UpdateOpts struct and updates an existing stack using the values
+// provided.
+func Update(c *gophercloud.ServiceClient, stackName, stackID string, opts UpdateOptsBuilder) UpdateResult {
+	var res UpdateResult
+
+	reqBody, err := opts.ToStackUpdateMap()
+	if err != nil {
+		res.Err = err
+		return res
+	}
+
+	// Send request to API
+	_, res.Err = perigee.Request("PUT", updateURL(c, stackName, stackID), perigee.Options{
+		MoreHeaders: c.AuthenticatedHeaders(),
+		ReqBody:     &reqBody,
+		OkCodes:     []int{202},
+	})
+	return res
+}
+
+// Delete deletes a stack based on the stack name and stack ID.
+func Delete(c *gophercloud.ServiceClient, stackName, stackID string) DeleteResult {
+	var res DeleteResult
+
+	// Send request to API
+	_, res.Err = perigee.Request("DELETE", deleteURL(c, stackName, stackID), perigee.Options{
+		MoreHeaders: c.AuthenticatedHeaders(),
+		OkCodes:     []int{204},
+	})
+	return res
+}
+
+// PreviewOptsBuilder is the interface options structs have to satisfy in order
+// to be used in the Preview operation in this package.
+type PreviewOptsBuilder interface {
+	ToStackPreviewMap() (map[string]interface{}, error)
+}
+
+// PreviewOpts contains the common options struct used in this package's Preview
+// operation.
+type PreviewOpts struct {
+	// (REQUIRED) The name of the stack. It must start with an alphabetic character.
+	Name string
+	// (REQUIRED) The timeout for stack creation in minutes.
+	Timeout int
+	// (OPTIONAL; REQUIRED IF Template IS EMPTY) The URL of the template to instantiate.
+	// This value is ignored if Template is supplied inline.
+	TemplateURL string
+	// (OPTIONAL; REQUIRED IF TemplateURL IS EMPTY) A template to instantiate. The value
+	// is a stringified version of the JSON/YAML template. Since the template will likely
+	// be located in a file, one way to set this variable is by using ioutil.ReadFile:
+	// import "io/ioutil"
+	// var opts stacks.CreateOpts
+	// b, err := ioutil.ReadFile("path/to/you/template/file.json")
+	// if err != nil {
+	//   // handle error...
+	// }
+	// opts.Template = string(b)
+	Template string
+	// (OPTIONAL) Enables or disables deletion of all stack resources when a stack
+	// creation fails. Default is true, meaning all resources are not deleted when
+	// stack creation fails.
+	DisableRollback Rollback
+	// (OPTIONAL) A stringified JSON environment for the stack.
+	Environment string
+	// (OPTIONAL) A map that maps file names to file contents. It can also be used
+	// to pass provider template contents. Example:
+	// Files: `{"myfile": "#!/bin/bash\necho 'Hello world' > /root/testfile.txt"}`
+	Files map[string]interface{}
+	// (OPTIONAL) User-defined parameters to pass to the template.
+	Parameters map[string]string
+}
+
+// ToStackPreviewMap casts a PreviewOpts struct to a map.
+func (opts PreviewOpts) ToStackPreviewMap() (map[string]interface{}, error) {
+	s := make(map[string]interface{})
+
+	if opts.Name == "" {
+		return s, errors.New("Required field 'Name' not provided.")
+	}
+	s["stack_name"] = opts.Name
+
+	if opts.Template != "" {
+		s["template"] = opts.Template
+	} else if opts.TemplateURL != "" {
+		s["template_url"] = opts.TemplateURL
+	} else {
+		return s, errors.New("Either Template or TemplateURL must be provided.")
+	}
+
+	if opts.DisableRollback != nil {
+		s["disable_rollback"] = &opts.DisableRollback
+	}
+
+	if opts.Environment != "" {
+		s["environment"] = opts.Environment
+	}
+	if opts.Files != nil {
+		s["files"] = opts.Files
+	}
+	if opts.Parameters != nil {
+		s["parameters"] = opts.Parameters
+	}
+
+	if opts.Timeout != 0 {
+		s["timeout_mins"] = opts.Timeout
+	}
+
+	return s, nil
+}
+
+// Preview accepts a PreviewOptsBuilder interface and creates a preview of a stack using the values
+// provided.
+func Preview(c *gophercloud.ServiceClient, opts PreviewOptsBuilder) PreviewResult {
+	var res PreviewResult
+
+	reqBody, err := opts.ToStackPreviewMap()
+	if err != nil {
+		res.Err = err
+		return res
+	}
+
+	// Send request to API
+	_, res.Err = perigee.Request("POST", previewURL(c), perigee.Options{
+		MoreHeaders: c.AuthenticatedHeaders(),
+		ReqBody:     &reqBody,
+		Results:     &res.Body,
+		OkCodes:     []int{200},
+	})
+	return res
+}
+
+// Abandon deletes the stack with the provided stackName and stackID, but leaves its
+// resources intact, and returns data describing the stack and its resources.
+func Abandon(c *gophercloud.ServiceClient, stackName, stackID string) AbandonResult {
+	var res AbandonResult
+
+	// Send request to API
+	_, res.Err = perigee.Request("DELETE", abandonURL(c, stackName, stackID), perigee.Options{
+		MoreHeaders: c.AuthenticatedHeaders(),
+		Results:     &res.Body,
+		OkCodes:     []int{200},
+	})
+	return res
+}
diff --git a/openstack/orchestration/v1/stacks/requests_test.go b/openstack/orchestration/v1/stacks/requests_test.go
new file mode 100644
index 0000000..1e32ca2
--- /dev/null
+++ b/openstack/orchestration/v1/stacks/requests_test.go
@@ -0,0 +1,217 @@
+package stacks
+
+import (
+	"testing"
+
+	"github.com/rackspace/gophercloud/pagination"
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func TestCreateStack(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleCreateSuccessfully(t, CreateOutput)
+
+	createOpts := CreateOpts{
+		Name:    "stackcreated",
+		Timeout: 60,
+		Template: `
+    {
+      "stack_name": "postman_stack",
+      "template": {
+        "heat_template_version": "2013-05-23",
+        "description": "Simple template to test heat commands",
+        "parameters": {
+          "flavor": {
+            "default": "m1.tiny",
+            "type": "string"
+          }
+        },
+        "resources": {
+          "hello_world": {
+            "type":"OS::Nova::Server",
+            "properties": {
+              "key_name": "heat_key",
+              "flavor": {
+                "get_param": "flavor"
+              },
+              "image": "ad091b52-742f-469e-8f3c-fd81cadf0743",
+              "user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n"
+            }
+          }
+        }
+      }
+    }`,
+		DisableRollback: Disable,
+	}
+	actual, err := Create(fake.ServiceClient(), createOpts).Extract()
+	th.AssertNoErr(t, err)
+
+	expected := CreateExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
+
+func TestAdoptStack(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleCreateSuccessfully(t, CreateOutput)
+
+	adoptOpts := AdoptOpts{
+		AdoptStackData: `{environment{parameters{}}}`,
+		Name:           "stackcreated",
+		Timeout:        60,
+		Template: `
+    {
+      "stack_name": "postman_stack",
+      "template": {
+        "heat_template_version": "2013-05-23",
+        "description": "Simple template to test heat commands",
+        "parameters": {
+          "flavor": {
+            "default": "m1.tiny",
+            "type": "string"
+          }
+        },
+        "resources": {
+          "hello_world": {
+            "type":"OS::Nova::Server",
+            "properties": {
+              "key_name": "heat_key",
+              "flavor": {
+                "get_param": "flavor"
+              },
+              "image": "ad091b52-742f-469e-8f3c-fd81cadf0743",
+              "user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n"
+            }
+          }
+        }
+      }
+    }`,
+		DisableRollback: Disable,
+	}
+	actual, err := Adopt(fake.ServiceClient(), adoptOpts).Extract()
+	th.AssertNoErr(t, err)
+
+	expected := CreateExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
+
+func TestListStack(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleListSuccessfully(t, FullListOutput)
+
+	count := 0
+	err := List(fake.ServiceClient(), nil).EachPage(func(page pagination.Page) (bool, error) {
+		count++
+		actual, err := ExtractStacks(page)
+		th.AssertNoErr(t, err)
+
+		th.CheckDeepEquals(t, ListExpected, actual)
+
+		return true, nil
+	})
+	th.AssertNoErr(t, err)
+	th.CheckEquals(t, count, 1)
+}
+
+func TestGetStack(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleGetSuccessfully(t, GetOutput)
+
+	actual, err := Get(fake.ServiceClient(), "postman_stack", "16ef0584-4458-41eb-87c8-0dc8d5f66c87").Extract()
+	th.AssertNoErr(t, err)
+
+	expected := GetExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
+
+func TestUpdateStack(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleUpdateSuccessfully(t)
+
+	updateOpts := UpdateOpts{
+		Template: `
+    {
+      "heat_template_version": "2013-05-23",
+      "description": "Simple template to test heat commands",
+      "parameters": {
+        "flavor": {
+          "default": "m1.tiny",
+          "type": "string"
+        }
+      },
+      "resources": {
+        "hello_world": {
+          "type":"OS::Nova::Server",
+          "properties": {
+            "key_name": "heat_key",
+            "flavor": {
+              "get_param": "flavor"
+            },
+            "image": "ad091b52-742f-469e-8f3c-fd81cadf0743",
+            "user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n"
+          }
+        }
+      }
+    }`,
+	}
+	err := Update(fake.ServiceClient(), "gophercloud-test-stack-2", "db6977b2-27aa-4775-9ae7-6213212d4ada", updateOpts).ExtractErr()
+	th.AssertNoErr(t, err)
+}
+
+func TestDeleteStack(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleDeleteSuccessfully(t)
+
+	err := Delete(fake.ServiceClient(), "gophercloud-test-stack-2", "db6977b2-27aa-4775-9ae7-6213212d4ada").ExtractErr()
+	th.AssertNoErr(t, err)
+}
+
+func TestPreviewStack(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandlePreviewSuccessfully(t, GetOutput)
+
+	previewOpts := PreviewOpts{
+		Name:    "stackcreated",
+		Timeout: 60,
+		Template: `
+    {
+      "stack_name": "postman_stack",
+      "template": {
+        "heat_template_version": "2013-05-23",
+        "description": "Simple template to test heat commands",
+        "parameters": {
+          "flavor": {
+            "default": "m1.tiny",
+            "type": "string"
+          }
+        },
+        "resources": {
+          "hello_world": {
+            "type":"OS::Nova::Server",
+            "properties": {
+              "key_name": "heat_key",
+              "flavor": {
+                "get_param": "flavor"
+              },
+              "image": "ad091b52-742f-469e-8f3c-fd81cadf0743",
+              "user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n"
+            }
+          }
+        }
+      }
+    }`,
+		DisableRollback: Disable,
+	}
+	actual, err := Preview(fake.ServiceClient(), previewOpts).Extract()
+	th.AssertNoErr(t, err)
+
+	expected := PreviewExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
diff --git a/openstack/orchestration/v1/stacks/results.go b/openstack/orchestration/v1/stacks/results.go
new file mode 100644
index 0000000..ff971e8
--- /dev/null
+++ b/openstack/orchestration/v1/stacks/results.go
@@ -0,0 +1,296 @@
+package stacks
+
+import (
+	"encoding/json"
+	"time"
+
+	"github.com/mitchellh/mapstructure"
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/pagination"
+)
+
+// CreatedStack represents the object extracted from a Create operation.
+type CreatedStack struct {
+	ID    string             `mapstructure:"id"`
+	Links []gophercloud.Link `mapstructure:"links"`
+}
+
+// CreateResult represents the result of a Create operation.
+type CreateResult struct {
+	gophercloud.Result
+}
+
+// Extract returns a pointer to a CreatedStack object and is called after a
+// Create operation.
+func (r CreateResult) Extract() (*CreatedStack, error) {
+	if r.Err != nil {
+		return nil, r.Err
+	}
+
+	var res struct {
+		Stack *CreatedStack `mapstructure:"stack"`
+	}
+
+	if err := mapstructure.Decode(r.Body, &res); err != nil {
+		return nil, err
+	}
+
+	return res.Stack, nil
+}
+
+// AdoptResult represents the result of an Adopt operation. AdoptResult has the
+// same form as CreateResult.
+type AdoptResult struct {
+	CreateResult
+}
+
+// StackPage is a pagination.Pager that is returned from a call to the List function.
+type StackPage struct {
+	pagination.SinglePageBase
+}
+
+// IsEmpty returns true if a ListResult contains no Stacks.
+func (r StackPage) IsEmpty() (bool, error) {
+	stacks, err := ExtractStacks(r)
+	if err != nil {
+		return true, err
+	}
+	return len(stacks) == 0, nil
+}
+
+// ListedStack represents an element in the slice extracted from a List operation.
+type ListedStack struct {
+	CreationTime time.Time          `mapstructure:"-"`
+	Description  string             `mapstructure:"description"`
+	ID           string             `mapstructure:"id"`
+	Links        []gophercloud.Link `mapstructure:"links"`
+	Name         string             `mapstructure:"stack_name"`
+	Status       string             `mapstructure:"stack_status"`
+	StatusReason string             `mapstructure:"stack_status_reason"`
+	UpdatedTime  time.Time          `mapstructure:"-"`
+}
+
+// ExtractStacks extracts and returns a slice of ListedStack. It is used while iterating
+// over a stacks.List call.
+func ExtractStacks(page pagination.Page) ([]ListedStack, error) {
+	var res struct {
+		Stacks []ListedStack `mapstructure:"stacks"`
+	}
+
+	err := mapstructure.Decode(page.(StackPage).Body, &res)
+	if err != nil {
+		return nil, err
+	}
+
+	rawStacks := (((page.(StackPage).Body).(map[string]interface{}))["stacks"]).([]interface{})
+	for i := range rawStacks {
+		thisStack := (rawStacks[i]).(map[string]interface{})
+
+		if t, ok := thisStack["creation_time"].(string); ok && t != "" {
+			creationTime, err := time.Parse(time.RFC3339, t)
+			if err != nil {
+				return res.Stacks, err
+			}
+			res.Stacks[i].CreationTime = creationTime
+		}
+
+		if t, ok := thisStack["updated_time"].(string); ok && t != "" {
+			updatedTime, err := time.Parse(time.RFC3339, t)
+			if err != nil {
+				return res.Stacks, err
+			}
+			res.Stacks[i].UpdatedTime = updatedTime
+		}
+	}
+
+	return res.Stacks, nil
+}
+
+// RetrievedStack represents the object extracted from a Get operation.
+type RetrievedStack struct {
+	Capabilities        []interface{}            `mapstructure:"capabilities"`
+	CreationTime        time.Time                `mapstructure:"-"`
+	Description         string                   `mapstructure:"description"`
+	DisableRollback     bool                     `mapstructure:"disable_rollback"`
+	ID                  string                   `mapstructure:"id"`
+	Links               []gophercloud.Link       `mapstructure:"links"`
+	NotificationTopics  []interface{}            `mapstructure:"notification_topics"`
+	Outputs             []map[string]interface{} `mapstructure:"outputs"`
+	Parameters          map[string]string        `mapstructure:"parameters"`
+	Name                string                   `mapstructure:"stack_name"`
+	Status              string                   `mapstructure:"stack_status"`
+	StatusReason        string                   `mapstructure:"stack_status_reason"`
+	TemplateDescription string                   `mapstructure:"template_description"`
+	Timeout             int                      `mapstructure:"timeout_mins"`
+	UpdatedTime         time.Time                `mapstructure:"-"`
+}
+
+// GetResult represents the result of a Get operation.
+type GetResult struct {
+	gophercloud.Result
+}
+
+// Extract returns a pointer to a RetrievedStack object and is called after a
+// Get operation.
+func (r GetResult) Extract() (*RetrievedStack, error) {
+	if r.Err != nil {
+		return nil, r.Err
+	}
+
+	var res struct {
+		Stack *RetrievedStack `mapstructure:"stack"`
+	}
+
+	config := &mapstructure.DecoderConfig{
+		Result:           &res,
+		WeaklyTypedInput: true,
+	}
+	decoder, err := mapstructure.NewDecoder(config)
+	if err != nil {
+		return nil, err
+	}
+
+	if err := decoder.Decode(r.Body); err != nil {
+		return nil, err
+	}
+
+	b := r.Body.(map[string]interface{})["stack"].(map[string]interface{})
+
+	if date, ok := b["creation_time"]; ok && date != nil {
+		t, err := time.Parse(time.RFC3339, date.(string))
+		if err != nil {
+			return nil, err
+		}
+		res.Stack.CreationTime = t
+	}
+
+	if date, ok := b["updated_time"]; ok && date != nil {
+		t, err := time.Parse(time.RFC3339, date.(string))
+		if err != nil {
+			return nil, err
+		}
+		res.Stack.UpdatedTime = t
+	}
+
+	return res.Stack, err
+}
+
+// UpdateResult represents the result of a Update operation.
+type UpdateResult struct {
+	gophercloud.ErrResult
+}
+
+// DeleteResult represents the result of a Delete operation.
+type DeleteResult struct {
+	gophercloud.ErrResult
+}
+
+// PreviewedStack represents the result of a Preview operation.
+type PreviewedStack struct {
+	Capabilities        []interface{}            `mapstructure:"capabilities"`
+	CreationTime        time.Time                `mapstructure:"-"`
+	Description         string                   `mapstructure:"description"`
+	DisableRollback     bool                     `mapstructure:"disable_rollback"`
+	ID                  string                   `mapstructure:"id"`
+	Links               []gophercloud.Link       `mapstructure:"links"`
+	Name                string                   `mapstructure:"stack_name"`
+	NotificationTopics  []interface{}            `mapstructure:"notification_topics"`
+	Parameters          map[string]string        `mapstructure:"parameters"`
+	Resources           []map[string]interface{} `mapstructure:"resources"`
+	Status              string                   `mapstructure:"stack_status"`
+	StatusReason        string                   `mapstructure:"stack_status_reason"`
+	TemplateDescription string                   `mapstructure:"template_description"`
+	Timeout             int                      `mapstructure:"timeout_mins"`
+	UpdatedTime         time.Time                `mapstructure:"-"`
+}
+
+// PreviewResult represents the result of a Preview operation.
+type PreviewResult struct {
+	gophercloud.Result
+}
+
+// Extract returns a pointer to a PreviewedStack object and is called after a
+// Preview operation.
+func (r PreviewResult) Extract() (*PreviewedStack, error) {
+	if r.Err != nil {
+		return nil, r.Err
+	}
+
+	var res struct {
+		Stack *PreviewedStack `mapstructure:"stack"`
+	}
+
+	config := &mapstructure.DecoderConfig{
+		Result:           &res,
+		WeaklyTypedInput: true,
+	}
+	decoder, err := mapstructure.NewDecoder(config)
+	if err != nil {
+		return nil, err
+	}
+
+	if err := decoder.Decode(r.Body); err != nil {
+		return nil, err
+	}
+
+	b := r.Body.(map[string]interface{})["stack"].(map[string]interface{})
+
+	if date, ok := b["creation_time"]; ok && date != nil {
+		t, err := time.Parse(time.RFC3339, date.(string))
+		if err != nil {
+			return nil, err
+		}
+		res.Stack.CreationTime = t
+	}
+
+	if date, ok := b["updated_time"]; ok && date != nil {
+		t, err := time.Parse(time.RFC3339, date.(string))
+		if err != nil {
+			return nil, err
+		}
+		res.Stack.UpdatedTime = t
+	}
+
+	return res.Stack, err
+}
+
+// AbandonedStack represents the result of an Abandon operation.
+type AbandonedStack struct {
+	Status    string                 `mapstructure:"status"`
+	Name      string                 `mapstructure:"name"`
+	Template  map[string]interface{} `mapstructure:"template"`
+	Action    string                 `mapstructure:"action"`
+	ID        string                 `mapstructure:"id"`
+	Resources map[string]interface{} `mapstructure:"resources"`
+}
+
+// AbandonResult represents the result of an Abandon operation.
+type AbandonResult struct {
+	gophercloud.Result
+}
+
+// Extract returns a pointer to an AbandonedStack object and is called after an
+// Abandon operation.
+func (r AbandonResult) Extract() (*AbandonedStack, error) {
+	if r.Err != nil {
+		return nil, r.Err
+	}
+
+	var res AbandonedStack
+
+	if err := mapstructure.Decode(r.Body, &res); err != nil {
+		return nil, err
+	}
+
+	return &res, nil
+}
+
+// String converts an AbandonResult to a string. This is useful to when passing
+// the result of an Abandon operation to an AdoptOpts AdoptStackData field.
+func (r AbandonResult) String() (string, error) {
+	out, err := json.Marshal(r)
+	if err != nil {
+		return "", err
+	}
+	return string(out), nil
+}
diff --git a/openstack/orchestration/v1/stacks/urls.go b/openstack/orchestration/v1/stacks/urls.go
new file mode 100644
index 0000000..3dd2bb3
--- /dev/null
+++ b/openstack/orchestration/v1/stacks/urls.go
@@ -0,0 +1,35 @@
+package stacks
+
+import "github.com/rackspace/gophercloud"
+
+func createURL(c *gophercloud.ServiceClient) string {
+	return c.ServiceURL("stacks")
+}
+
+func adoptURL(c *gophercloud.ServiceClient) string {
+	return createURL(c)
+}
+
+func listURL(c *gophercloud.ServiceClient) string {
+	return createURL(c)
+}
+
+func getURL(c *gophercloud.ServiceClient, name, id string) string {
+	return c.ServiceURL("stacks", name, id)
+}
+
+func updateURL(c *gophercloud.ServiceClient, name, id string) string {
+	return getURL(c, name, id)
+}
+
+func deleteURL(c *gophercloud.ServiceClient, name, id string) string {
+	return getURL(c, name, id)
+}
+
+func previewURL(c *gophercloud.ServiceClient) string {
+	return c.ServiceURL("stacks", "preview")
+}
+
+func abandonURL(c *gophercloud.ServiceClient, name, id string) string {
+	return c.ServiceURL("stacks", name, id, "abandon")
+}
diff --git a/openstack/orchestration/v1/stacktemplates/doc.go b/openstack/orchestration/v1/stacktemplates/doc.go
new file mode 100644
index 0000000..5af0bd6
--- /dev/null
+++ b/openstack/orchestration/v1/stacktemplates/doc.go
@@ -0,0 +1,8 @@
+// Package stacktemplates provides operations for working with Heat templates.
+// A Cloud Orchestration template is a portable file, written in a user-readable
+// language, that describes how a set of resources should be assembled and what
+// software should be installed in order to produce a working stack. The template
+// specifies what resources should be used, what attributes can be set, and other
+// parameters that are critical to the successful, repeatable automation of a
+// specific application stack.
+package stacktemplates
diff --git a/openstack/orchestration/v1/stacktemplates/fixtures.go b/openstack/orchestration/v1/stacktemplates/fixtures.go
new file mode 100644
index 0000000..71fa808
--- /dev/null
+++ b/openstack/orchestration/v1/stacktemplates/fixtures.go
@@ -0,0 +1,118 @@
+package stacktemplates
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+// GetExpected represents the expected object from a Get request.
+var GetExpected = &Template{
+	Description:         "Simple template to test heat commands",
+	HeatTemplateVersion: "2013-05-23",
+	Parameters: map[string]interface{}{
+		"flavor": map[string]interface{}{
+			"default": "m1.tiny",
+			"type":    "string",
+		},
+	},
+	Resources: map[string]interface{}{
+		"hello_world": map[string]interface{}{
+			"type": "OS::Nova::Server",
+			"properties": map[string]interface{}{
+				"key_name": "heat_key",
+				"flavor": map[string]interface{}{
+					"get_param": "flavor",
+				},
+				"image":     "ad091b52-742f-469e-8f3c-fd81cadf0743",
+				"user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n",
+			},
+		},
+	},
+}
+
+// GetOutput represents the response body from a Get request.
+const GetOutput = `
+{
+  "heat_template_version": "2013-05-23",
+  "description": "Simple template to test heat commands",
+  "parameters": {
+    "flavor": {
+      "default": "m1.tiny",
+      "type": "string"
+    }
+  },
+  "resources": {
+    "hello_world": {
+      "type": "OS::Nova::Server",
+      "properties": {
+        "key_name": "heat_key",
+        "flavor": {
+          "get_param": "flavor"
+        },
+        "image": "ad091b52-742f-469e-8f3c-fd81cadf0743",
+        "user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n"
+      }
+    }
+  }
+}`
+
+// HandleGetSuccessfully creates an HTTP handler at `/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87/template`
+// on the test handler mux that responds with a `Get` response.
+func HandleGetSuccessfully(t *testing.T, output string) {
+	th.Mux.HandleFunc("/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87/template", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+
+		w.Header().Set("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprintf(w, output)
+	})
+}
+
+// ValidateExpected represents the expected object from a Validate request.
+var ValidateExpected = &ValidatedTemplate{
+	Description: "Simple template to test heat commands",
+	Parameters: map[string]interface{}{
+		"flavor": map[string]interface{}{
+			"Default":     "m1.tiny",
+			"Type":        "String",
+			"NoEcho":      "false",
+			"Description": "",
+			"Label":       "flavor",
+		},
+	},
+}
+
+// ValidateOutput represents the response body from a Validate request.
+const ValidateOutput = `
+{
+	"Description": "Simple template to test heat commands",
+	"Parameters": {
+		"flavor": {
+			"Default": "m1.tiny",
+			"Type": "String",
+			"NoEcho": "false",
+			"Description": "",
+			"Label": "flavor"
+		}
+	}
+}`
+
+// HandleValidateSuccessfully creates an HTTP handler at `/validate`
+// on the test handler mux that responds with a `Validate` response.
+func HandleValidateSuccessfully(t *testing.T, output string) {
+	th.Mux.HandleFunc("/validate", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "POST")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+
+		w.Header().Set("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprintf(w, output)
+	})
+}
diff --git a/openstack/orchestration/v1/stacktemplates/requests.go b/openstack/orchestration/v1/stacktemplates/requests.go
new file mode 100644
index 0000000..5f8aba9
--- /dev/null
+++ b/openstack/orchestration/v1/stacktemplates/requests.go
@@ -0,0 +1,64 @@
+package stacktemplates
+
+import (
+	"fmt"
+
+	"github.com/racker/perigee"
+	"github.com/rackspace/gophercloud"
+)
+
+// Get retreives data for the given stack template.
+func Get(c *gophercloud.ServiceClient, stackName, stackID string) GetResult {
+	var res GetResult
+	_, res.Err = perigee.Request("GET", getURL(c, stackName, stackID), perigee.Options{
+		MoreHeaders: c.AuthenticatedHeaders(),
+		Results:     &res.Body,
+		OkCodes:     []int{200},
+	})
+	return res
+}
+
+// ValidateOptsBuilder describes struct types that can be accepted by the Validate call.
+// The ValidateOpts struct in this package does.
+type ValidateOptsBuilder interface {
+	ToStackTemplateValidateMap() (map[string]interface{}, error)
+}
+
+// ValidateOpts specifies the template validation parameters.
+type ValidateOpts struct {
+	Template    map[string]interface{}
+	TemplateURL string
+}
+
+// ToStackTemplateValidateMap assembles a request body based on the contents of a ValidateOpts.
+func (opts ValidateOpts) ToStackTemplateValidateMap() (map[string]interface{}, error) {
+	vo := make(map[string]interface{})
+	if opts.Template != nil {
+		vo["template"] = opts.Template
+		return vo, nil
+	}
+	if opts.TemplateURL != "" {
+		vo["template_url"] = opts.TemplateURL
+		return vo, nil
+	}
+	return vo, fmt.Errorf("One of Template or TemplateURL is required.")
+}
+
+// Validate validates the given stack template.
+func Validate(c *gophercloud.ServiceClient, opts ValidateOptsBuilder) ValidateResult {
+	var res ValidateResult
+
+	reqBody, err := opts.ToStackTemplateValidateMap()
+	if err != nil {
+		res.Err = err
+		return res
+	}
+
+	_, res.Err = perigee.Request("POST", validateURL(c), perigee.Options{
+		MoreHeaders: c.AuthenticatedHeaders(),
+		ReqBody:     reqBody,
+		Results:     &res.Body,
+		OkCodes:     []int{200},
+	})
+	return res
+}
diff --git a/openstack/orchestration/v1/stacktemplates/requests_test.go b/openstack/orchestration/v1/stacktemplates/requests_test.go
new file mode 100644
index 0000000..d31c4ac
--- /dev/null
+++ b/openstack/orchestration/v1/stacktemplates/requests_test.go
@@ -0,0 +1,57 @@
+package stacktemplates
+
+import (
+	"testing"
+
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func TestGetTemplate(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleGetSuccessfully(t, GetOutput)
+
+	actual, err := Get(fake.ServiceClient(), "postman_stack", "16ef0584-4458-41eb-87c8-0dc8d5f66c87").Extract()
+	th.AssertNoErr(t, err)
+
+	expected := GetExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
+
+func TestValidateTemplate(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleValidateSuccessfully(t, ValidateOutput)
+
+	opts := ValidateOpts{
+		Template: map[string]interface{}{
+			"heat_template_version": "2013-05-23",
+			"description":           "Simple template to test heat commands",
+			"parameters": map[string]interface{}{
+				"flavor": map[string]interface{}{
+					"default": "m1.tiny",
+					"type":    "string",
+				},
+			},
+			"resources": map[string]interface{}{
+				"hello_world": map[string]interface{}{
+					"type": "OS::Nova::Server",
+					"properties": map[string]interface{}{
+						"key_name": "heat_key",
+						"flavor": map[string]interface{}{
+							"get_param": "flavor",
+						},
+						"image":     "ad091b52-742f-469e-8f3c-fd81cadf0743",
+						"user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n",
+					},
+				},
+			},
+		},
+	}
+	actual, err := Validate(fake.ServiceClient(), opts).Extract()
+	th.AssertNoErr(t, err)
+
+	expected := ValidateExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
diff --git a/openstack/orchestration/v1/stacktemplates/results.go b/openstack/orchestration/v1/stacktemplates/results.go
new file mode 100644
index 0000000..ac2f24b
--- /dev/null
+++ b/openstack/orchestration/v1/stacktemplates/results.go
@@ -0,0 +1,60 @@
+package stacktemplates
+
+import (
+	"github.com/mitchellh/mapstructure"
+	"github.com/rackspace/gophercloud"
+)
+
+// Template represents a stack template.
+type Template struct {
+	Description         string                 `mapstructure:"description"`
+	HeatTemplateVersion string                 `mapstructure:"heat_template_version"`
+	Parameters          map[string]interface{} `mapstructure:"parameters"`
+	Resources           map[string]interface{} `mapstructure:"resources"`
+}
+
+// GetResult represents the result of a Get operation.
+type GetResult struct {
+	gophercloud.Result
+}
+
+// Extract returns a pointer to a Template object and is called after a
+// Get operation.
+func (r GetResult) Extract() (*Template, error) {
+	if r.Err != nil {
+		return nil, r.Err
+	}
+
+	var res Template
+	if err := mapstructure.Decode(r.Body, &res); err != nil {
+		return nil, err
+	}
+
+	return &res, nil
+}
+
+// ValidatedTemplate represents the parsed object returned from a Validate request.
+type ValidatedTemplate struct {
+	Description string
+	Parameters  map[string]interface{}
+}
+
+// ValidateResult represents the result of a Validate operation.
+type ValidateResult struct {
+	gophercloud.Result
+}
+
+// Extract returns a pointer to a ValidatedTemplate object and is called after a
+// Validate operation.
+func (r ValidateResult) Extract() (*ValidatedTemplate, error) {
+	if r.Err != nil {
+		return nil, r.Err
+	}
+
+	var res ValidatedTemplate
+	if err := mapstructure.Decode(r.Body, &res); err != nil {
+		return nil, err
+	}
+
+	return &res, nil
+}
diff --git a/openstack/orchestration/v1/stacktemplates/urls.go b/openstack/orchestration/v1/stacktemplates/urls.go
new file mode 100644
index 0000000..c30b7ca
--- /dev/null
+++ b/openstack/orchestration/v1/stacktemplates/urls.go
@@ -0,0 +1,11 @@
+package stacktemplates
+
+import "github.com/rackspace/gophercloud"
+
+func getURL(c *gophercloud.ServiceClient, stackName, stackID string) string {
+	return c.ServiceURL("stacks", stackName, stackID, "template")
+}
+
+func validateURL(c *gophercloud.ServiceClient) string {
+	return c.ServiceURL("validate")
+}
diff --git a/params.go b/params.go
index 948783b..4d0f1e6 100644
--- a/params.go
+++ b/params.go
@@ -144,6 +144,17 @@
 						params.Add(tags[0], strconv.FormatInt(v.Int(), 10))
 					case reflect.Bool:
 						params.Add(tags[0], strconv.FormatBool(v.Bool()))
+					case reflect.Slice:
+						switch v.Type().Elem() {
+						case reflect.TypeOf(0):
+							for i := 0; i < v.Len(); i++ {
+								params.Add(tags[0], strconv.FormatInt(v.Index(i).Int(), 10))
+							}
+						default:
+							for i := 0; i < v.Len(); i++ {
+								params.Add(tags[0], v.Index(i).String())
+							}
+						}
 					}
 				} else {
 					// Otherwise, the field is not set.
diff --git a/params_test.go b/params_test.go
index 4a2c9fe..2f40eec 100644
--- a/params_test.go
+++ b/params_test.go
@@ -34,16 +34,23 @@
 }
 
 func TestBuildQueryString(t *testing.T) {
+	type testVar string
 	opts := struct {
-		J int    `q:"j"`
-		R string `q:"r,required"`
-		C bool   `q:"c"`
+		J  int       `q:"j"`
+		R  string    `q:"r,required"`
+		C  bool      `q:"c"`
+		S  []string  `q:"s"`
+		TS []testVar `q:"ts"`
+		TI []int     `q:"ti"`
 	}{
-		J: 2,
-		R: "red",
-		C: true,
+		J:  2,
+		R:  "red",
+		C:  true,
+		S:  []string{"one", "two", "three"},
+		TS: []testVar{"a", "b"},
+		TI: []int{1, 2},
 	}
-	expected := &url.URL{RawQuery: "c=true&j=2&r=red"}
+	expected := &url.URL{RawQuery: "c=true&j=2&r=red&s=one&s=two&s=three&ti=1&ti=2&ts=a&ts=b"}
 	actual, err := BuildQueryString(&opts)
 	if err != nil {
 		t.Errorf("Error building query string: %v", err)
@@ -51,9 +58,12 @@
 	th.CheckDeepEquals(t, expected, actual)
 
 	opts = struct {
-		J int    `q:"j"`
-		R string `q:"r,required"`
-		C bool   `q:"c"`
+		J  int       `q:"j"`
+		R  string    `q:"r,required"`
+		C  bool      `q:"c"`
+		S  []string  `q:"s"`
+		TS []testVar `q:"ts"`
+		TI []int     `q:"ti"`
 	}{
 		J: 2,
 		C: true,
diff --git a/rackspace/client.go b/rackspace/client.go
index 45199a4..439d846 100644
--- a/rackspace/client.go
+++ b/rackspace/client.go
@@ -187,3 +187,13 @@
 	}
 	return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
 }
+
+// NewOrchestrationV1 creates a ServiceClient that may be used to access the v1 orchestration service.
+func NewOrchestrationV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
+	eo.ApplyDefaults("orchestration")
+	url, err := client.EndpointLocator(eo)
+	if err != nil {
+		return nil, err
+	}
+	return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
+}
diff --git a/rackspace/orchestration/v1/buildinfo/delegate.go b/rackspace/orchestration/v1/buildinfo/delegate.go
new file mode 100644
index 0000000..c834e5c
--- /dev/null
+++ b/rackspace/orchestration/v1/buildinfo/delegate.go
@@ -0,0 +1,11 @@
+package buildinfo
+
+import (
+	"github.com/rackspace/gophercloud"
+	os "github.com/rackspace/gophercloud/openstack/orchestration/v1/buildinfo"
+)
+
+// Get retreives build info data for the Heat deployment.
+func Get(c *gophercloud.ServiceClient) os.GetResult {
+	return os.Get(c)
+}
diff --git a/rackspace/orchestration/v1/buildinfo/delegate_test.go b/rackspace/orchestration/v1/buildinfo/delegate_test.go
new file mode 100644
index 0000000..b25a690
--- /dev/null
+++ b/rackspace/orchestration/v1/buildinfo/delegate_test.go
@@ -0,0 +1,21 @@
+package buildinfo
+
+import (
+	"testing"
+
+	os "github.com/rackspace/gophercloud/openstack/orchestration/v1/buildinfo"
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func TestGetTemplate(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleGetSuccessfully(t, os.GetOutput)
+
+	actual, err := Get(fake.ServiceClient()).Extract()
+	th.AssertNoErr(t, err)
+
+	expected := os.GetExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
diff --git a/rackspace/orchestration/v1/buildinfo/doc.go b/rackspace/orchestration/v1/buildinfo/doc.go
new file mode 100644
index 0000000..183e8df
--- /dev/null
+++ b/rackspace/orchestration/v1/buildinfo/doc.go
@@ -0,0 +1,2 @@
+// Package buildinfo provides build information about heat deployments.
+package buildinfo
diff --git a/rackspace/orchestration/v1/stackevents/delegate.go b/rackspace/orchestration/v1/stackevents/delegate.go
new file mode 100644
index 0000000..08675de
--- /dev/null
+++ b/rackspace/orchestration/v1/stackevents/delegate.go
@@ -0,0 +1,27 @@
+package stackevents
+
+import (
+	"github.com/rackspace/gophercloud"
+	os "github.com/rackspace/gophercloud/openstack/orchestration/v1/stackevents"
+	"github.com/rackspace/gophercloud/pagination"
+)
+
+// Find retreives stack events for the given stack name.
+func Find(c *gophercloud.ServiceClient, stackName string) os.FindResult {
+	return os.Find(c, stackName)
+}
+
+// List makes a request against the API to list resources for the given stack.
+func List(c *gophercloud.ServiceClient, stackName, stackID string, opts os.ListOptsBuilder) pagination.Pager {
+	return os.List(c, stackName, stackID, opts)
+}
+
+// ListResourceEvents makes a request against the API to list resources for the given stack.
+func ListResourceEvents(c *gophercloud.ServiceClient, stackName, stackID, resourceName string, opts os.ListResourceEventsOptsBuilder) pagination.Pager {
+	return os.ListResourceEvents(c, stackName, stackID, resourceName, opts)
+}
+
+// Get retreives data for the given stack resource.
+func Get(c *gophercloud.ServiceClient, stackName, stackID, resourceName, eventID string) os.GetResult {
+	return os.Get(c, stackName, stackID, resourceName, eventID)
+}
diff --git a/rackspace/orchestration/v1/stackevents/delegate_test.go b/rackspace/orchestration/v1/stackevents/delegate_test.go
new file mode 100644
index 0000000..e1c0bc8
--- /dev/null
+++ b/rackspace/orchestration/v1/stackevents/delegate_test.go
@@ -0,0 +1,72 @@
+package stackevents
+
+import (
+	"testing"
+
+	os "github.com/rackspace/gophercloud/openstack/orchestration/v1/stackevents"
+	"github.com/rackspace/gophercloud/pagination"
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func TestFindEvents(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleFindSuccessfully(t, os.FindOutput)
+
+	actual, err := Find(fake.ServiceClient(), "postman_stack").Extract()
+	th.AssertNoErr(t, err)
+
+	expected := os.FindExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
+
+func TestList(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleListSuccessfully(t, os.ListOutput)
+
+	count := 0
+	err := List(fake.ServiceClient(), "hello_world", "49181cd6-169a-4130-9455-31185bbfc5bf", nil).EachPage(func(page pagination.Page) (bool, error) {
+		count++
+		actual, err := os.ExtractEvents(page)
+		th.AssertNoErr(t, err)
+
+		th.CheckDeepEquals(t, os.ListExpected, actual)
+
+		return true, nil
+	})
+	th.AssertNoErr(t, err)
+	th.CheckEquals(t, count, 1)
+}
+
+func TestListResourceEvents(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleListResourceEventsSuccessfully(t, os.ListResourceEventsOutput)
+
+	count := 0
+	err := ListResourceEvents(fake.ServiceClient(), "hello_world", "49181cd6-169a-4130-9455-31185bbfc5bf", "my_resource", nil).EachPage(func(page pagination.Page) (bool, error) {
+		count++
+		actual, err := os.ExtractEvents(page)
+		th.AssertNoErr(t, err)
+
+		th.CheckDeepEquals(t, os.ListResourceEventsExpected, actual)
+
+		return true, nil
+	})
+	th.AssertNoErr(t, err)
+	th.CheckEquals(t, count, 1)
+}
+
+func TestGetEvent(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleGetSuccessfully(t, os.GetOutput)
+
+	actual, err := Get(fake.ServiceClient(), "hello_world", "49181cd6-169a-4130-9455-31185bbfc5bf", "my_resource", "93940999-7d40-44ae-8de4-19624e7b8d18").Extract()
+	th.AssertNoErr(t, err)
+
+	expected := os.GetExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
diff --git a/rackspace/orchestration/v1/stackevents/doc.go b/rackspace/orchestration/v1/stackevents/doc.go
new file mode 100644
index 0000000..dfd6ef6
--- /dev/null
+++ b/rackspace/orchestration/v1/stackevents/doc.go
@@ -0,0 +1,3 @@
+// Package stackevents provides operations for finding, listing, and retrieving
+// stack events.
+package stackevents
diff --git a/rackspace/orchestration/v1/stackresources/delegate.go b/rackspace/orchestration/v1/stackresources/delegate.go
new file mode 100644
index 0000000..cb7be28
--- /dev/null
+++ b/rackspace/orchestration/v1/stackresources/delegate.go
@@ -0,0 +1,42 @@
+package stackresources
+
+import (
+	"github.com/rackspace/gophercloud"
+	os "github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources"
+	"github.com/rackspace/gophercloud/pagination"
+)
+
+// Find retreives stack resources for the given stack name.
+func Find(c *gophercloud.ServiceClient, stackName string) os.FindResult {
+	return os.Find(c, stackName)
+}
+
+// List makes a request against the API to list resources for the given stack.
+func List(c *gophercloud.ServiceClient, stackName, stackID string, opts os.ListOptsBuilder) pagination.Pager {
+	return os.List(c, stackName, stackID, opts)
+}
+
+// Get retreives data for the given stack resource.
+func Get(c *gophercloud.ServiceClient, stackName, stackID, resourceName string) os.GetResult {
+	return os.Get(c, stackName, stackID, resourceName)
+}
+
+// Metadata retreives the metadata for the given stack resource.
+func Metadata(c *gophercloud.ServiceClient, stackName, stackID, resourceName string) os.MetadataResult {
+	return os.Metadata(c, stackName, stackID, resourceName)
+}
+
+// ListTypes makes a request against the API to list resource types.
+func ListTypes(c *gophercloud.ServiceClient) pagination.Pager {
+	return os.ListTypes(c)
+}
+
+// Schema retreives the schema for the given resource type.
+func Schema(c *gophercloud.ServiceClient, resourceType string) os.SchemaResult {
+	return os.Schema(c, resourceType)
+}
+
+// Template retreives the template representation for the given resource type.
+func Template(c *gophercloud.ServiceClient, resourceType string) os.TemplateResult {
+	return os.Template(c, resourceType)
+}
diff --git a/rackspace/orchestration/v1/stackresources/delegate_test.go b/rackspace/orchestration/v1/stackresources/delegate_test.go
new file mode 100644
index 0000000..18e9614
--- /dev/null
+++ b/rackspace/orchestration/v1/stackresources/delegate_test.go
@@ -0,0 +1,108 @@
+package stackresources
+
+import (
+	"testing"
+
+	os "github.com/rackspace/gophercloud/openstack/orchestration/v1/stackresources"
+	"github.com/rackspace/gophercloud/pagination"
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func TestFindResources(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleFindSuccessfully(t, os.FindOutput)
+
+	actual, err := Find(fake.ServiceClient(), "hello_world").Extract()
+	th.AssertNoErr(t, err)
+
+	expected := os.FindExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
+
+func TestListResources(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleListSuccessfully(t, os.ListOutput)
+
+	count := 0
+	err := List(fake.ServiceClient(), "hello_world", "49181cd6-169a-4130-9455-31185bbfc5bf", nil).EachPage(func(page pagination.Page) (bool, error) {
+		count++
+		actual, err := os.ExtractResources(page)
+		th.AssertNoErr(t, err)
+
+		th.CheckDeepEquals(t, os.ListExpected, actual)
+
+		return true, nil
+	})
+	th.AssertNoErr(t, err)
+	th.CheckEquals(t, count, 1)
+}
+
+func TestGetResource(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleGetSuccessfully(t, os.GetOutput)
+
+	actual, err := Get(fake.ServiceClient(), "teststack", "0b1771bd-9336-4f2b-ae86-a80f971faf1e", "wordpress_instance").Extract()
+	th.AssertNoErr(t, err)
+
+	expected := os.GetExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
+
+func TestResourceMetadata(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleMetadataSuccessfully(t, os.MetadataOutput)
+
+	actual, err := Metadata(fake.ServiceClient(), "teststack", "0b1771bd-9336-4f2b-ae86-a80f971faf1e", "wordpress_instance").Extract()
+	th.AssertNoErr(t, err)
+
+	expected := os.MetadataExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
+
+func TestListResourceTypes(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleListTypesSuccessfully(t, os.ListTypesOutput)
+
+	count := 0
+	err := ListTypes(fake.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) {
+		count++
+		actual, err := os.ExtractResourceTypes(page)
+		th.AssertNoErr(t, err)
+
+		th.CheckDeepEquals(t, os.ListTypesExpected, actual)
+
+		return true, nil
+	})
+	th.AssertNoErr(t, err)
+	th.CheckEquals(t, 1, count)
+}
+
+func TestGetResourceSchema(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleGetSchemaSuccessfully(t, os.GetSchemaOutput)
+
+	actual, err := Schema(fake.ServiceClient(), "OS::Heat::AResourceName").Extract()
+	th.AssertNoErr(t, err)
+
+	expected := os.GetSchemaExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
+
+func TestGetResourceTemplate(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleGetTemplateSuccessfully(t, os.GetTemplateOutput)
+
+	actual, err := Template(fake.ServiceClient(), "OS::Heat::AResourceName").Extract()
+	th.AssertNoErr(t, err)
+
+	expected := os.GetTemplateExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
diff --git a/rackspace/orchestration/v1/stackresources/doc.go b/rackspace/orchestration/v1/stackresources/doc.go
new file mode 100644
index 0000000..e4f8b08
--- /dev/null
+++ b/rackspace/orchestration/v1/stackresources/doc.go
@@ -0,0 +1,5 @@
+// Package stackresources provides operations for working with stack resources.
+// A resource is a template artifact that represents some component of your
+// desired architecture (a Cloud Server, a group of scaled Cloud Servers, a load
+// balancer, some configuration management system, and so forth).
+package stackresources
diff --git a/rackspace/orchestration/v1/stacks/delegate.go b/rackspace/orchestration/v1/stacks/delegate.go
new file mode 100644
index 0000000..f7e387f
--- /dev/null
+++ b/rackspace/orchestration/v1/stacks/delegate.go
@@ -0,0 +1,49 @@
+package stacks
+
+import (
+	"github.com/rackspace/gophercloud"
+	os "github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks"
+	"github.com/rackspace/gophercloud/pagination"
+)
+
+// Create accepts an os.CreateOpts struct and creates a new stack using the values
+// provided.
+func Create(c *gophercloud.ServiceClient, opts os.CreateOptsBuilder) os.CreateResult {
+	return os.Create(c, opts)
+}
+
+// Adopt accepts an os.AdoptOpts struct and creates a new stack from existing stack
+// resources using the values provided.
+func Adopt(c *gophercloud.ServiceClient, opts os.AdoptOptsBuilder) os.AdoptResult {
+	return os.Adopt(c, opts)
+}
+
+// List accepts an os.ListOpts struct and lists stacks based on the options provided.
+func List(c *gophercloud.ServiceClient, opts os.ListOptsBuilder) pagination.Pager {
+	return os.List(c, opts)
+}
+
+// Get retreives a stack based on the stack name and stack ID.
+func Get(c *gophercloud.ServiceClient, stackName, stackID string) os.GetResult {
+	return os.Get(c, stackName, stackID)
+}
+
+// Update accepts an os.UpdateOpts struct and updates a stack based on the options provided.
+func Update(c *gophercloud.ServiceClient, stackName, stackID string, opts os.UpdateOptsBuilder) os.UpdateResult {
+	return os.Update(c, stackName, stackID, opts)
+}
+
+// Delete deletes a stack based on the stack name and stack ID provided.
+func Delete(c *gophercloud.ServiceClient, stackName, stackID string) os.DeleteResult {
+	return os.Delete(c, stackName, stackID)
+}
+
+// Preview provides a preview of a stack based on the options provided.
+func Preview(c *gophercloud.ServiceClient, opts os.PreviewOptsBuilder) os.PreviewResult {
+	return os.Preview(c, opts)
+}
+
+// Abandon abandons a stack, keeping the resources available.
+func Abandon(c *gophercloud.ServiceClient, stackName, stackID string) os.AbandonResult {
+	return os.Abandon(c, stackName, stackID)
+}
diff --git a/rackspace/orchestration/v1/stacks/delegate_test.go b/rackspace/orchestration/v1/stacks/delegate_test.go
new file mode 100644
index 0000000..a1fb393
--- /dev/null
+++ b/rackspace/orchestration/v1/stacks/delegate_test.go
@@ -0,0 +1,461 @@
+package stacks
+
+import (
+	"testing"
+
+	os "github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks"
+	"github.com/rackspace/gophercloud/pagination"
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func TestCreateStack(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleCreateSuccessfully(t, CreateOutput)
+
+	createOpts := os.CreateOpts{
+		Name:    "stackcreated",
+		Timeout: 60,
+		Template: `{
+      "outputs": {
+        "db_host": {
+          "value": {
+            "get_attr": [
+            "db",
+            "hostname"
+            ]
+          }
+        }
+      },
+      "heat_template_version": "2014-10-16",
+      "description": "HEAT template for creating a Cloud Database.\n",
+      "parameters": {
+        "db_name": {
+          "default": "wordpress",
+          "type": "string",
+          "description": "the name for the database",
+          "constraints": [
+          {
+            "length": {
+              "max": 64,
+              "min": 1
+            },
+            "description": "must be between 1 and 64 characters"
+          },
+          {
+            "allowed_pattern": "[a-zA-Z][a-zA-Z0-9]*",
+            "description": "must begin with a letter and contain only alphanumeric characters."
+          }
+          ]
+        },
+        "db_instance_name": {
+          "default": "Cloud_DB",
+          "type": "string",
+          "description": "the database instance name"
+        },
+        "db_username": {
+          "default": "admin",
+          "hidden": true,
+          "type": "string",
+          "description": "database admin account username",
+          "constraints": [
+          {
+            "length": {
+              "max": 16,
+              "min": 1
+                },
+              "description": "must be between 1 and 16 characters"
+            },
+            {
+              "allowed_pattern": "[a-zA-Z][a-zA-Z0-9]*",
+              "description": "must begin with a letter and contain only alphanumeric characters."
+            }
+          ]
+          },
+          "db_volume_size": {
+            "default": 30,
+            "type": "number",
+            "description": "database volume size (in GB)",
+            "constraints": [
+            {
+              "range": {
+                "max": 1024,
+                "min": 1
+              },
+              "description": "must be between 1 and 1024 GB"
+            }
+            ]
+          },
+          "db_flavor": {
+            "default": "1GB Instance",
+            "type": "string",
+            "description": "database instance size",
+            "constraints": [
+            {
+              "description": "must be a valid cloud database flavor",
+              "allowed_values": [
+              "1GB Instance",
+              "2GB Instance",
+              "4GB Instance",
+              "8GB Instance",
+              "16GB Instance"
+              ]
+            }
+            ]
+          },
+        "db_password": {
+          "default": "admin",
+          "hidden": true,
+          "type": "string",
+          "description": "database admin account password",
+          "constraints": [
+          {
+            "length": {
+              "max": 41,
+              "min": 1
+            },
+            "description": "must be between 1 and 14 characters"
+          },
+          {
+            "allowed_pattern": "[a-zA-Z0-9]*",
+            "description": "must contain only alphanumeric characters."
+          }
+          ]
+        }
+      },
+      "resources": {
+        "db": {
+          "type": "OS::Trove::Instance",
+          "properties": {
+            "flavor": {
+              "get_param": "db_flavor"
+            },
+            "size": {
+              "get_param": "db_volume_size"
+            },
+            "users": [
+            {
+              "password": {
+                "get_param": "db_password"
+              },
+              "name": {
+                "get_param": "db_username"
+              },
+              "databases": [
+              {
+                "get_param": "db_name"
+              }
+              ]
+            }
+            ],
+            "name": {
+              "get_param": "db_instance_name"
+            },
+            "databases": [
+            {
+              "name": {
+                "get_param": "db_name"
+              }
+            }
+            ]
+          }
+        }
+      }
+    }`,
+		DisableRollback: os.Disable,
+	}
+	actual, err := Create(fake.ServiceClient(), createOpts).Extract()
+	th.AssertNoErr(t, err)
+
+	expected := CreateExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
+
+func TestAdoptStack(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleCreateSuccessfully(t, CreateOutput)
+
+	adoptOpts := os.AdoptOpts{
+		AdoptStackData: `{\"environment\":{\"parameters\":{}},    \"status\":\"COMPLETE\",\"name\": \"trovestack\",\n  \"template\": {\n    \"outputs\": {\n      \"db_host\": {\n        \"value\": {\n          \"get_attr\": [\n            \"db\",\n            \"hostname\"\n          ]\n        }\n      }\n    },\n    \"heat_template_version\": \"2014-10-16\",\n    \"description\": \"HEAT template for creating a Cloud Database.\\n\",\n    \"parameters\": {\n      \"db_instance_name\": {\n        \"default\": \"Cloud_DB\",\n        \"type\": \"string\",\n        \"description\": \"the database instance name\"\n      },\n      \"db_flavor\": {\n        \"default\": \"1GB Instance\",\n        \"type\": \"string\",\n        \"description\": \"database instance size\",\n        \"constraints\": [\n          {\n            \"description\": \"must be a valid cloud database flavor\",\n            \"allowed_values\": [\n              \"1GB Instance\",\n              \"2GB Instance\",\n              \"4GB Instance\",\n              \"8GB Instance\",\n              \"16GB Instance\"\n            ]\n          }\n        ]\n      },\n      \"db_password\": {\n        \"default\": \"admin\",\n        \"hidden\": true,\n        \"type\": \"string\",\n        \"description\": \"database admin account password\",\n        \"constraints\": [\n          {\n            \"length\": {\n              \"max\": 41,\n              \"min\": 1\n            },\n            \"description\": \"must be between 1 and 14 characters\"\n          },\n          {\n            \"allowed_pattern\": \"[a-zA-Z0-9]*\",\n            \"description\": \"must contain only alphanumeric characters.\"\n          }\n        ]\n      },\n      \"db_name\": {\n        \"default\": \"wordpress\",\n        \"type\": \"string\",\n        \"description\": \"the name for the database\",\n        \"constraints\": [\n          {\n            \"length\": {\n              \"max\": 64,\n              \"min\": 1\n            },\n            \"description\": \"must be between 1 and 64 characters\"\n          },\n          {\n            \"allowed_pattern\": \"[a-zA-Z][a-zA-Z0-9]*\",\n            \"description\": \"must begin with a letter and contain only alphanumeric characters.\"\n          }\n        ]\n      },\n      \"db_username\": {\n        \"default\": \"admin\",\n        \"hidden\": true,\n        \"type\": \"string\",\n        \"description\": \"database admin account username\",\n        \"constraints\": [\n          {\n            \"length\": {\n              \"max\": 16,\n              \"min\": 1\n            },\n            \"description\": \"must be between 1 and 16 characters\"\n          },\n          {\n            \"allowed_pattern\": \"[a-zA-Z][a-zA-Z0-9]*\",\n            \"description\": \"must begin with a letter and contain only alphanumeric characters.\"\n          }\n        ]\n      },\n      \"db_volume_size\": {\n        \"default\": 30,\n        \"type\": \"number\",\n        \"description\": \"database volume size (in GB)\",\n        \"constraints\": [\n          {\n            \"range\": {\n              \"max\": 1024,\n              \"min\": 1\n            },\n            \"description\": \"must be between 1 and 1024 GB\"\n          }\n        ]\n      }\n    },\n    \"resources\": {\n      \"db\": {\n        \"type\": \"OS::Trove::Instance\",\n        \"properties\": {\n          \"flavor\": {\n            \"get_param\": \"db_flavor\"\n          },\n          \"databases\": [\n            {\n              \"name\": {\n                \"get_param\": \"db_name\"\n              }\n            }\n          ],\n          \"users\": [\n            {\n              \"password\": {\n                \"get_param\": \"db_password\"\n              },\n              \"name\": {\n                \"get_param\": \"db_username\"\n              },\n              \"databases\": [\n                {\n                  \"get_param\": \"db_name\"\n                }\n              ]\n            }\n          ],\n          \"name\": {\n            \"get_param\": \"db_instance_name\"\n          },\n          \"size\": {\n            \"get_param\": \"db_volume_size\"\n          }\n        }\n      }\n    }\n  },\n  \"action\": \"CREATE\",\n  \"id\": \"exxxxd-7xx5-4xxb-bxx2-cxxxxxx5\",\n  \"resources\": {\n    \"db\": {\n      \"status\": \"COMPLETE\",\n      \"name\": \"db\",\n      \"resource_data\": {},\n      \"resource_id\": \"exxxx2-9xx0-4xxxb-bxx2-dxxxxxx4\",\n      \"action\": \"CREATE\",\n      \"type\": \"OS::Trove::Instance\",\n      \"metadata\": {}\n    }\n  }\n},`,
+		Name:           "stackadopted",
+		Timeout:        60,
+		Template: `{
+      "outputs": {
+        "db_host": {
+          "value": {
+            "get_attr": [
+            "db",
+            "hostname"
+            ]
+          }
+        }
+      },
+      "heat_template_version": "2014-10-16",
+      "description": "HEAT template for creating a Cloud Database.\n",
+      "parameters": {
+        "db_name": {
+          "default": "wordpress",
+          "type": "string",
+          "description": "the name for the database",
+          "constraints": [
+          {
+            "length": {
+              "max": 64,
+              "min": 1
+            },
+            "description": "must be between 1 and 64 characters"
+          },
+          {
+            "allowed_pattern": "[a-zA-Z][a-zA-Z0-9]*",
+            "description": "must begin with a letter and contain only alphanumeric characters."
+          }
+          ]
+        },
+        "db_instance_name": {
+          "default": "Cloud_DB",
+          "type": "string",
+          "description": "the database instance name"
+        },
+        "db_username": {
+          "default": "admin",
+          "hidden": true,
+          "type": "string",
+          "description": "database admin account username",
+          "constraints": [
+          {
+            "length": {
+              "max": 16,
+              "min": 1
+            },
+            "description": "must be between 1 and 16 characters"
+          },
+          {
+            "allowed_pattern": "[a-zA-Z][a-zA-Z0-9]*",
+            "description": "must begin with a letter and contain only alphanumeric characters."
+          }
+          ]
+        },
+        "db_volume_size": {
+          "default": 30,
+          "type": "number",
+          "description": "database volume size (in GB)",
+          "constraints": [
+          {
+            "range": {
+              "max": 1024,
+              "min": 1
+            },
+            "description": "must be between 1 and 1024 GB"
+          }
+          ]
+        },
+        "db_flavor": {
+          "default": "1GB Instance",
+          "type": "string",
+          "description": "database instance size",
+          "constraints": [
+          {
+            "description": "must be a valid cloud database flavor",
+            "allowed_values": [
+            "1GB Instance",
+            "2GB Instance",
+            "4GB Instance",
+            "8GB Instance",
+            "16GB Instance"
+            ]
+          }
+          ]
+        },
+        "db_password": {
+          "default": "admin",
+          "hidden": true,
+          "type": "string",
+          "description": "database admin account password",
+          "constraints": [
+          {
+            "length": {
+              "max": 41,
+              "min": 1
+            },
+            "description": "must be between 1 and 14 characters"
+          },
+          {
+            "allowed_pattern": "[a-zA-Z0-9]*",
+            "description": "must contain only alphanumeric characters."
+          }
+          ]
+        }
+      },
+      "resources": {
+        "db": {
+          "type": "OS::Trove::Instance",
+          "properties": {
+            "flavor": {
+              "get_param": "db_flavor"
+            },
+            "size": {
+              "get_param": "db_volume_size"
+            },
+            "users": [
+            {
+              "password": {
+                "get_param": "db_password"
+              },
+              "name": {
+                "get_param": "db_username"
+              },
+              "databases": [
+              {
+                "get_param": "db_name"
+              }
+              ]
+            }
+            ],
+            "name": {
+              "get_param": "db_instance_name"
+            },
+            "databases": [
+            {
+              "name": {
+                "get_param": "db_name"
+              }
+            }
+            ]
+          }
+        }
+      }
+    }`,
+		DisableRollback: os.Disable,
+	}
+	actual, err := Adopt(fake.ServiceClient(), adoptOpts).Extract()
+	th.AssertNoErr(t, err)
+
+	expected := CreateExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
+
+func TestListStack(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleListSuccessfully(t, os.FullListOutput)
+
+	count := 0
+	err := List(fake.ServiceClient(), nil).EachPage(func(page pagination.Page) (bool, error) {
+		count++
+		actual, err := os.ExtractStacks(page)
+		th.AssertNoErr(t, err)
+
+		th.CheckDeepEquals(t, os.ListExpected, actual)
+
+		return true, nil
+	})
+	th.AssertNoErr(t, err)
+	th.CheckEquals(t, count, 1)
+}
+
+func TestUpdateStack(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleUpdateSuccessfully(t)
+
+	updateOpts := os.UpdateOpts{
+		Template: `
+    {
+      "heat_template_version": "2013-05-23",
+      "description": "Simple template to test heat commands",
+      "parameters": {
+        "flavor": {
+          "default": "m1.tiny",
+          "type": "string"
+        }
+      },
+      "resources": {
+        "hello_world": {
+          "type":"OS::Nova::Server",
+          "properties": {
+            "key_name": "heat_key",
+            "flavor": {
+              "get_param": "flavor"
+            },
+            "image": "ad091b52-742f-469e-8f3c-fd81cadf0743",
+            "user_data": "#!/bin/bash -xv\necho \"hello world\" &gt; /root/hello-world.txt\n"
+          }
+        }
+      }
+    }`,
+	}
+	err := Update(fake.ServiceClient(), "gophercloud-test-stack-2", "db6977b2-27aa-4775-9ae7-6213212d4ada", updateOpts).ExtractErr()
+	th.AssertNoErr(t, err)
+}
+
+func TestDeleteStack(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleDeleteSuccessfully(t)
+
+	err := Delete(fake.ServiceClient(), "gophercloud-test-stack-2", "db6977b2-27aa-4775-9ae7-6213212d4ada").ExtractErr()
+	th.AssertNoErr(t, err)
+}
+
+func TestPreviewStack(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandlePreviewSuccessfully(t, os.GetOutput)
+
+	previewOpts := os.PreviewOpts{
+		Name:    "stackcreated",
+		Timeout: 60,
+		Template: `
+    {
+      "stack_name": "postman_stack",
+      "template": {
+        "heat_template_version": "2013-05-23",
+        "description": "Simple template to test heat commands",
+        "parameters": {
+          "flavor": {
+            "default": "m1.tiny",
+            "type": "string"
+          }
+        },
+        "resources": {
+          "hello_world": {
+            "type":"OS::Nova::Server",
+            "properties": {
+              "key_name": "heat_key",
+              "flavor": {
+                "get_param": "flavor"
+              },
+              "image": "ad091b52-742f-469e-8f3c-fd81cadf0743",
+              "user_data": "#!/bin/bash -xv\necho \"hello world\" &gt; /root/hello-world.txt\n"
+            }
+          }
+        }
+      }
+    }`,
+		DisableRollback: os.Disable,
+	}
+	actual, err := Preview(fake.ServiceClient(), previewOpts).Extract()
+	th.AssertNoErr(t, err)
+
+	expected := os.PreviewExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
+
+/*
+func TestAbandonStack(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleAbandonSuccessfully(t)
+
+	//actual, err := Abandon(fake.ServiceClient(), "postman_stack", "16ef0584-4458-41eb-87c8-0dc8d5f66c87").Extract()
+	//th.AssertNoErr(t, err)
+	res := Abandon(fake.ServiceClient(), "postman_stack", "16ef0584-4458-41eb-87c8-0dc8d5f66c87") //.Extract()
+	th.AssertNoErr(t, res.Err)
+	t.Logf("actual: %+v", res)
+
+	//expected := os.AbandonExpected
+	//th.AssertDeepEquals(t, expected, actual)
+}
+*/
diff --git a/rackspace/orchestration/v1/stacks/doc.go b/rackspace/orchestration/v1/stacks/doc.go
new file mode 100644
index 0000000..19231b5
--- /dev/null
+++ b/rackspace/orchestration/v1/stacks/doc.go
@@ -0,0 +1,8 @@
+// Package stacks provides operation for working with Heat stacks. A stack is a
+// group of resources (servers, load balancers, databases, and so forth)
+// combined to fulfill a useful purpose. Based on a template, Heat orchestration
+// engine creates an instantiated set of resources (a stack) to run the
+// application framework or component specified (in the template). A stack is a
+// running instance of a template. The result of creating a stack is a deployment
+// of the application framework or component.
+package stacks
diff --git a/rackspace/orchestration/v1/stacks/fixtures.go b/rackspace/orchestration/v1/stacks/fixtures.go
new file mode 100644
index 0000000..c9afeb1
--- /dev/null
+++ b/rackspace/orchestration/v1/stacks/fixtures.go
@@ -0,0 +1,32 @@
+package stacks
+
+import (
+	"github.com/rackspace/gophercloud"
+	os "github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks"
+)
+
+// CreateExpected represents the expected object from a Create request.
+var CreateExpected = &os.CreatedStack{
+	ID: "b663e18a-4767-4cdf-9db5-9c8cc13cc38a",
+	Links: []gophercloud.Link{
+		gophercloud.Link{
+			Href: "https://ord.orchestration.api.rackspacecloud.com/v1/864477/stacks/stackcreated/b663e18a-4767-4cdf-9db5-9c8cc13cc38a",
+			Rel:  "self",
+		},
+	},
+}
+
+// CreateOutput represents the response body from a Create request.
+const CreateOutput = `
+{
+  "stack": {
+    "id": "b663e18a-4767-4cdf-9db5-9c8cc13cc38a",
+    "links": [
+    {
+      "href": "https://ord.orchestration.api.rackspacecloud.com/v1/864477/stacks/stackcreated/b663e18a-4767-4cdf-9db5-9c8cc13cc38a",
+      "rel": "self"
+    }
+    ]
+  }
+}
+`
diff --git a/rackspace/orchestration/v1/stacktemplates/delegate.go b/rackspace/orchestration/v1/stacktemplates/delegate.go
new file mode 100644
index 0000000..3b5d46e
--- /dev/null
+++ b/rackspace/orchestration/v1/stacktemplates/delegate.go
@@ -0,0 +1,16 @@
+package stacktemplates
+
+import (
+	"github.com/rackspace/gophercloud"
+	os "github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates"
+)
+
+// Get retreives data for the given stack template.
+func Get(c *gophercloud.ServiceClient, stackName, stackID string) os.GetResult {
+	return os.Get(c, stackName, stackID)
+}
+
+// Validate validates the given stack template.
+func Validate(c *gophercloud.ServiceClient, opts os.ValidateOptsBuilder) os.ValidateResult {
+	return os.Validate(c, opts)
+}
diff --git a/rackspace/orchestration/v1/stacktemplates/delegate_test.go b/rackspace/orchestration/v1/stacktemplates/delegate_test.go
new file mode 100644
index 0000000..d4006c4
--- /dev/null
+++ b/rackspace/orchestration/v1/stacktemplates/delegate_test.go
@@ -0,0 +1,58 @@
+package stacktemplates
+
+import (
+	"testing"
+
+	os "github.com/rackspace/gophercloud/openstack/orchestration/v1/stacktemplates"
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func TestGetTemplate(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleGetSuccessfully(t, os.GetOutput)
+
+	actual, err := Get(fake.ServiceClient(), "postman_stack", "16ef0584-4458-41eb-87c8-0dc8d5f66c87").Extract()
+	th.AssertNoErr(t, err)
+
+	expected := os.GetExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
+
+func TestValidateTemplate(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	os.HandleValidateSuccessfully(t, os.ValidateOutput)
+
+	opts := os.ValidateOpts{
+		Template: map[string]interface{}{
+			"heat_template_version": "2013-05-23",
+			"description":           "Simple template to test heat commands",
+			"parameters": map[string]interface{}{
+				"flavor": map[string]interface{}{
+					"default": "m1.tiny",
+					"type":    "string",
+				},
+			},
+			"resources": map[string]interface{}{
+				"hello_world": map[string]interface{}{
+					"type": "OS::Nova::Server",
+					"properties": map[string]interface{}{
+						"key_name": "heat_key",
+						"flavor": map[string]interface{}{
+							"get_param": "flavor",
+						},
+						"image":     "ad091b52-742f-469e-8f3c-fd81cadf0743",
+						"user_data": "#!/bin/bash -xv\necho \"hello world\" &gt; /root/hello-world.txt\n",
+					},
+				},
+			},
+		},
+	}
+	actual, err := Validate(fake.ServiceClient(), opts).Extract()
+	th.AssertNoErr(t, err)
+
+	expected := os.ValidateExpected
+	th.AssertDeepEquals(t, expected, actual)
+}
diff --git a/rackspace/orchestration/v1/stacktemplates/doc.go b/rackspace/orchestration/v1/stacktemplates/doc.go
new file mode 100644
index 0000000..5af0bd6
--- /dev/null
+++ b/rackspace/orchestration/v1/stacktemplates/doc.go
@@ -0,0 +1,8 @@
+// Package stacktemplates provides operations for working with Heat templates.
+// A Cloud Orchestration template is a portable file, written in a user-readable
+// language, that describes how a set of resources should be assembled and what
+// software should be installed in order to produce a working stack. The template
+// specifies what resources should be used, what attributes can be set, and other
+// parameters that are critical to the successful, repeatable automation of a
+// specific application stack.
+package stacktemplates