Moving calls to client helper while I'm at it
diff --git a/_site/openstack/compute/v2/flavors/requests.go b/_site/openstack/compute/v2/flavors/requests.go
new file mode 100644
index 0000000..469c69d
--- /dev/null
+++ b/_site/openstack/compute/v2/flavors/requests.go
@@ -0,0 +1,72 @@
+package flavors
+
+import (
+	"github.com/racker/perigee"
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/pagination"
+)
+
+// ListOptsBuilder allows extensions to add additional parameters to the
+// List request.
+type ListOptsBuilder interface {
+	ToFlavorListParams() (string, error)
+}
+
+// ListOpts helps control the results returned by the List() function.
+// For example, a flavor with a minDisk field of 10 will not be returned if you specify MinDisk set to 20.
+// Typically, software will use the last ID of the previous call to List to set the Marker for the current call.
+type ListOpts struct {
+
+	// ChangesSince, if provided, instructs List to return only those things which have changed since the timestamp provided.
+	ChangesSince string `q:"changes-since"`
+
+	// MinDisk and MinRAM, if provided, elides flavors which do not meet your criteria.
+	MinDisk int `q:"minDisk"`
+	MinRAM  int `q:"minRam"`
+
+	// Marker and Limit control paging.
+	// Marker instructs List where to start listing from.
+	Marker string `q:"marker"`
+
+	// Limit instructs List to refrain from sending excessively large lists of flavors.
+	Limit int `q:"limit"`
+}
+
+// ToFlavorListParams formats a ListOpts into a query string.
+func (opts ListOpts) ToFlavorListParams() (string, error) {
+	q, err := gophercloud.BuildQueryString(opts)
+	if err != nil {
+		return "", err
+	}
+	return q.String(), nil
+}
+
+// List instructs OpenStack to provide a list of flavors.
+// You may provide criteria by which List curtails its results for easier processing.
+// See ListOpts for more details.
+func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
+	url := listURL(client)
+	if opts != nil {
+		query, err := opts.ToFlavorListParams()
+		if err != nil {
+			return pagination.Pager{Err: err}
+		}
+		url += query
+	}
+	createPage := func(r pagination.LastHTTPResponse) pagination.Page {
+		return FlavorPage{pagination.LinkedPageBase{LastHTTPResponse: r}}
+	}
+
+	return pagination.NewPager(client, url, createPage)
+}
+
+// Get instructs OpenStack to provide details on a single flavor, identified by its ID.
+// Use ExtractFlavor to convert its result into a Flavor.
+func Get(client *gophercloud.ServiceClient, id string) GetResult {
+	var gr GetResult
+	gr.Err = perigee.Get(getURL(client, id), perigee.Options{
+		Results:     &gr.Resp,
+		MoreHeaders: client.Provider.AuthenticatedHeaders(),
+	})
+	return gr
+}
diff --git a/_site/openstack/compute/v2/flavors/requests_test.go b/_site/openstack/compute/v2/flavors/requests_test.go
new file mode 100644
index 0000000..bc9b82e
--- /dev/null
+++ b/_site/openstack/compute/v2/flavors/requests_test.go
@@ -0,0 +1,129 @@
+package flavors
+
+import (
+	"fmt"
+	"net/http"
+	"reflect"
+	"testing"
+
+	"github.com/rackspace/gophercloud/pagination"
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+const tokenID = "blerb"
+
+func TestListFlavors(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	th.Mux.HandleFunc("/flavors/detail", 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")
+		r.ParseForm()
+		marker := r.Form.Get("marker")
+		switch marker {
+		case "":
+			fmt.Fprintf(w, `
+					{
+						"flavors": [
+							{
+								"id": "1",
+								"name": "m1.tiny",
+								"disk": 1,
+								"ram": 512,
+								"vcpus": 1
+							},
+							{
+								"id": "2",
+								"name": "m2.small",
+								"disk": 10,
+								"ram": 1024,
+								"vcpus": 2
+							}
+						],
+						"flavors_links": [
+							{
+								"href": "%s/flavors/detail?marker=2",
+								"rel": "next"
+							}
+						]
+					}
+				`, th.Server.URL)
+		case "2":
+			fmt.Fprintf(w, `{ "flavors": [] }`)
+		default:
+			t.Fatalf("Unexpected marker: [%s]", marker)
+		}
+	})
+
+	pages := 0
+	err := List(fake.ServiceClient(), &ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
+		pages++
+
+		actual, err := ExtractFlavors(page)
+		if err != nil {
+			return false, err
+		}
+
+		expected := []Flavor{
+			Flavor{ID: "1", Name: "m1.tiny", Disk: 1, RAM: 512, VCPUs: 1},
+			Flavor{ID: "2", Name: "m2.small", Disk: 10, RAM: 1024, VCPUs: 2},
+		}
+
+		if !reflect.DeepEqual(expected, actual) {
+			t.Errorf("Expected %#v, but was %#v", expected, actual)
+		}
+
+		return true, nil
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+	if pages != 1 {
+		t.Errorf("Expected one page, got %d", pages)
+	}
+}
+
+func TestGetFlavor(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	th.Mux.HandleFunc("/flavors/12345", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+		w.Header().Add("Content-Type", "application/json")
+		fmt.Fprintf(w, `
+			{
+				"flavor": {
+					"id": "1",
+					"name": "m1.tiny",
+					"disk": 1,
+					"ram": 512,
+					"vcpus": 1,
+					"rxtx_factor": 1
+				}
+			}
+		`)
+	})
+
+	actual, err := Get(fake.ServiceClient(), "12345").Extract()
+	if err != nil {
+		t.Fatalf("Unable to get flavor: %v", err)
+	}
+
+	expected := &Flavor{
+		ID:         "1",
+		Name:       "m1.tiny",
+		Disk:       1,
+		RAM:        512,
+		VCPUs:      1,
+		RxTxFactor: 1,
+	}
+	if !reflect.DeepEqual(expected, actual) {
+		t.Errorf("Expected %#v, but was %#v", expected, actual)
+	}
+}
diff --git a/_site/openstack/compute/v2/flavors/results.go b/_site/openstack/compute/v2/flavors/results.go
new file mode 100644
index 0000000..1e274e3
--- /dev/null
+++ b/_site/openstack/compute/v2/flavors/results.go
@@ -0,0 +1,122 @@
+package flavors
+
+import (
+	"errors"
+	"reflect"
+
+	"github.com/mitchellh/mapstructure"
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/pagination"
+)
+
+// ErrCannotInterpret is returned by an Extract call if the response body doesn't have the expected structure.
+var ErrCannotInterpet = errors.New("Unable to interpret a response body.")
+
+// GetResult temporarily holds the reponse from a Get call.
+type GetResult struct {
+	gophercloud.CommonResult
+}
+
+// Extract provides access to the individual Flavor returned by the Get function.
+func (gr GetResult) Extract() (*Flavor, error) {
+	if gr.Err != nil {
+		return nil, gr.Err
+	}
+
+	var result struct {
+		Flavor Flavor `mapstructure:"flavor"`
+	}
+
+	cfg := &mapstructure.DecoderConfig{
+		DecodeHook: defaulter,
+		Result:     &result,
+	}
+	decoder, err := mapstructure.NewDecoder(cfg)
+	if err != nil {
+		return nil, err
+	}
+	err = decoder.Decode(gr.Resp)
+	return &result.Flavor, err
+}
+
+// Flavor records represent (virtual) hardware configurations for server resources in a region.
+type Flavor struct {
+	// The Id field contains the flavor's unique identifier.
+	// For example, this identifier will be useful when specifying which hardware configuration to use for a new server instance.
+	ID string `mapstructure:"id"`
+
+	// The Disk and RA< fields provide a measure of storage space offered by the flavor, in GB and MB, respectively.
+	Disk int `mapstructure:"disk"`
+	RAM  int `mapstructure:"ram"`
+
+	// The Name field provides a human-readable moniker for the flavor.
+	Name string `mapstructure:"name"`
+
+	RxTxFactor float64 `mapstructure:"rxtx_factor"`
+
+	// Swap indicates how much space is reserved for swap.
+	// If not provided, this field will be set to 0.
+	Swap int `mapstructure:"swap"`
+
+	// VCPUs indicates how many (virtual) CPUs are available for this flavor.
+	VCPUs int `mapstructure:"vcpus"`
+}
+
+// FlavorPage contains a single page of the response from a List call.
+type FlavorPage struct {
+	pagination.LinkedPageBase
+}
+
+// IsEmpty determines if a page contains any results.
+func (p FlavorPage) IsEmpty() (bool, error) {
+	flavors, err := ExtractFlavors(p)
+	if err != nil {
+		return true, err
+	}
+	return len(flavors) == 0, nil
+}
+
+// NextPageURL uses the response's embedded link reference to navigate to the next page of results.
+func (p FlavorPage) NextPageURL() (string, error) {
+	type resp struct {
+		Links []gophercloud.Link `mapstructure:"flavors_links"`
+	}
+
+	var r resp
+	err := mapstructure.Decode(p.Body, &r)
+	if err != nil {
+		return "", err
+	}
+
+	return gophercloud.ExtractNextURL(r.Links)
+}
+
+func defaulter(from, to reflect.Kind, v interface{}) (interface{}, error) {
+	if (from == reflect.String) && (to == reflect.Int) {
+		return 0, nil
+	}
+	return v, nil
+}
+
+// ExtractFlavors provides access to the list of flavors in a page acquired from the List operation.
+func ExtractFlavors(page pagination.Page) ([]Flavor, error) {
+	casted := page.(FlavorPage).Body
+	var container struct {
+		Flavors []Flavor `mapstructure:"flavors"`
+	}
+
+	cfg := &mapstructure.DecoderConfig{
+		DecodeHook: defaulter,
+		Result:     &container,
+	}
+	decoder, err := mapstructure.NewDecoder(cfg)
+	if err != nil {
+		return container.Flavors, err
+	}
+	err = decoder.Decode(casted)
+	if err != nil {
+		return container.Flavors, err
+	}
+
+	return container.Flavors, nil
+}
diff --git a/_site/openstack/compute/v2/flavors/urls.go b/_site/openstack/compute/v2/flavors/urls.go
new file mode 100644
index 0000000..683c107
--- /dev/null
+++ b/_site/openstack/compute/v2/flavors/urls.go
@@ -0,0 +1,13 @@
+package flavors
+
+import (
+	"github.com/rackspace/gophercloud"
+)
+
+func getURL(client *gophercloud.ServiceClient, id string) string {
+	return client.ServiceURL("flavors", id)
+}
+
+func listURL(client *gophercloud.ServiceClient) string {
+	return client.ServiceURL("flavors", "detail")
+}
diff --git a/_site/openstack/compute/v2/flavors/urls_test.go b/_site/openstack/compute/v2/flavors/urls_test.go
new file mode 100644
index 0000000..069da24
--- /dev/null
+++ b/_site/openstack/compute/v2/flavors/urls_test.go
@@ -0,0 +1,26 @@
+package flavors
+
+import (
+	"testing"
+
+	"github.com/rackspace/gophercloud"
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+const endpoint = "http://localhost:57909/"
+
+func endpointClient() *gophercloud.ServiceClient {
+	return &gophercloud.ServiceClient{Endpoint: endpoint}
+}
+
+func TestGetURL(t *testing.T) {
+	actual := getURL(endpointClient(), "foo")
+	expected := endpoint + "flavors/foo"
+	th.CheckEquals(t, expected, actual)
+}
+
+func TestListURL(t *testing.T) {
+	actual := listURL(endpointClient())
+	expected := endpoint + "flavors/detail"
+	th.CheckEquals(t, expected, actual)
+}
diff --git a/_site/openstack/compute/v2/images/requests.go b/_site/openstack/compute/v2/images/requests.go
new file mode 100644
index 0000000..d901f6e
--- /dev/null
+++ b/_site/openstack/compute/v2/images/requests.go
@@ -0,0 +1,71 @@
+package images
+
+import (
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/pagination"
+
+	"github.com/racker/perigee"
+)
+
+// ListOptsBuilder allows extensions to add additional parameters to the
+// List request.
+type ListOptsBuilder interface {
+	ToImageListParams() (string, error)
+}
+
+// ListOpts contain options for limiting the number of Images returned from a call to ListDetail.
+type ListOpts struct {
+	// When the image last changed status (in date-time format).
+	ChangesSince string `q:"changes-since"`
+	// The number of Images to return.
+	Limit int `q:"limit"`
+	// UUID of the Image at which to set a marker.
+	Marker string `q:"marker"`
+	// The name of the Image.
+	Name string `q:"name:"`
+	// The name of the Server (in URL format).
+	Server string `q:"server"`
+	// The current status of the Image.
+	Status string `q:"status"`
+	// The value of the type of image (e.g. BASE, SERVER, ALL)
+	Type string `q:"type"`
+}
+
+// ToImageListParams formats a ListOpts into a query string.
+func (opts ListOpts) ToImageListParams() (string, error) {
+	q, err := gophercloud.BuildQueryString(opts)
+	if err != nil {
+		return "", err
+	}
+	return q.String(), nil
+}
+
+// ListDetail enumerates the available images.
+func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
+	url := listDetailURL(client)
+	if opts != nil {
+		query, err := opts.ToImageListParams()
+		if err != nil {
+			return pagination.Pager{Err: err}
+		}
+		url += query
+	}
+
+	createPage := func(r pagination.LastHTTPResponse) pagination.Page {
+		return ImagePage{pagination.LinkedPageBase{LastHTTPResponse: r}}
+	}
+
+	return pagination.NewPager(client, url, createPage)
+}
+
+// Get acquires additional detail about a specific image by ID.
+// Use ExtractImage() to intepret the result as an openstack Image.
+func Get(client *gophercloud.ServiceClient, id string) GetResult {
+	var result GetResult
+	_, result.Err = perigee.Request("GET", getURL(client, id), perigee.Options{
+		MoreHeaders: client.Provider.AuthenticatedHeaders(),
+		Results:     &result.Resp,
+		OkCodes:     []int{200},
+	})
+	return result
+}
diff --git a/_site/openstack/compute/v2/images/requests_test.go b/_site/openstack/compute/v2/images/requests_test.go
new file mode 100644
index 0000000..2dfa88b
--- /dev/null
+++ b/_site/openstack/compute/v2/images/requests_test.go
@@ -0,0 +1,175 @@
+package images
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"reflect"
+	"testing"
+
+	"github.com/rackspace/gophercloud/pagination"
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func TestListImages(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	th.Mux.HandleFunc("/images/detail", 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")
+		r.ParseForm()
+		marker := r.Form.Get("marker")
+		switch marker {
+		case "":
+			fmt.Fprintf(w, `
+				{
+					"images": [
+						{
+							"status": "ACTIVE",
+							"updated": "2014-09-23T12:54:56Z",
+							"id": "f3e4a95d-1f4f-4989-97ce-f3a1fb8c04d7",
+							"OS-EXT-IMG-SIZE:size": 476704768,
+							"name": "F17-x86_64-cfntools",
+							"created": "2014-09-23T12:54:52Z",
+							"minDisk": 0,
+							"progress": 100,
+							"minRam": 0,
+							"metadata": {}
+						},
+						{
+							"status": "ACTIVE",
+							"updated": "2014-09-23T12:51:43Z",
+							"id": "f90f6034-2570-4974-8351-6b49732ef2eb",
+							"OS-EXT-IMG-SIZE:size": 13167616,
+							"name": "cirros-0.3.2-x86_64-disk",
+							"created": "2014-09-23T12:51:42Z",
+							"minDisk": 0,
+							"progress": 100,
+							"minRam": 0,
+							"metadata": {}
+						}
+					]
+				}
+			`)
+		case "2":
+			fmt.Fprintf(w, `{ "images": [] }`)
+		default:
+			t.Fatalf("Unexpected marker: [%s]", marker)
+		}
+	})
+
+	pages := 0
+	options := &ListOpts{Limit: 2}
+	err := ListDetail(fake.ServiceClient(), options).EachPage(func(page pagination.Page) (bool, error) {
+		pages++
+
+		actual, err := ExtractImages(page)
+		if err != nil {
+			return false, err
+		}
+
+		expected := []Image{
+			Image{
+				ID:       "f3e4a95d-1f4f-4989-97ce-f3a1fb8c04d7",
+				Name:     "F17-x86_64-cfntools",
+				Created:  "2014-09-23T12:54:52Z",
+				Updated:  "2014-09-23T12:54:56Z",
+				MinDisk:  0,
+				MinRAM:   0,
+				Progress: 100,
+				Status:   "ACTIVE",
+			},
+			Image{
+				ID:       "f90f6034-2570-4974-8351-6b49732ef2eb",
+				Name:     "cirros-0.3.2-x86_64-disk",
+				Created:  "2014-09-23T12:51:42Z",
+				Updated:  "2014-09-23T12:51:43Z",
+				MinDisk:  0,
+				MinRAM:   0,
+				Progress: 100,
+				Status:   "ACTIVE",
+			},
+		}
+
+		if !reflect.DeepEqual(expected, actual) {
+			t.Errorf("Unexpected page contents: expected %#v, got %#v", expected, actual)
+		}
+
+		return false, nil
+	})
+
+	if err != nil {
+		t.Fatalf("EachPage error: %v", err)
+	}
+	if pages != 1 {
+		t.Errorf("Expected one page, got %d", pages)
+	}
+}
+
+func TestGetImage(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	th.Mux.HandleFunc("/images/12345678", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+		w.Header().Add("Content-Type", "application/json")
+		fmt.Fprintf(w, `
+			{
+				"image": {
+					"status": "ACTIVE",
+					"updated": "2014-09-23T12:54:56Z",
+					"id": "f3e4a95d-1f4f-4989-97ce-f3a1fb8c04d7",
+					"OS-EXT-IMG-SIZE:size": 476704768,
+					"name": "F17-x86_64-cfntools",
+					"created": "2014-09-23T12:54:52Z",
+					"minDisk": 0,
+					"progress": 100,
+					"minRam": 0,
+					"metadata": {}
+				}
+			}
+		`)
+	})
+
+	actual, err := Get(fake.ServiceClient(), "12345678").Extract()
+	if err != nil {
+		t.Fatalf("Unexpected error from Get: %v", err)
+	}
+
+	expected := &Image{
+		Status:   "ACTIVE",
+		Updated:  "2014-09-23T12:54:56Z",
+		ID:       "f3e4a95d-1f4f-4989-97ce-f3a1fb8c04d7",
+		Name:     "F17-x86_64-cfntools",
+		Created:  "2014-09-23T12:54:52Z",
+		MinDisk:  0,
+		Progress: 100,
+		MinRAM:   0,
+	}
+
+	if !reflect.DeepEqual(expected, actual) {
+		t.Errorf("Expected %#v, but got %#v", expected, actual)
+	}
+}
+
+func TestNextPageURL(t *testing.T) {
+	var page ImagePage
+	var body map[string]interface{}
+	bodyString := []byte(`{"images":{"links":[{"href":"http://192.154.23.87/12345/images/image3","rel":"next"},{"href":"http://192.154.23.87/12345/images/image1","rel":"previous"}]}}`)
+	err := json.Unmarshal(bodyString, &body)
+	if err != nil {
+		t.Fatalf("Error unmarshaling data into page body: %v", err)
+	}
+	page.Body = body
+
+	expected := "http://192.154.23.87/12345/images/image3"
+	actual, err := page.NextPageURL()
+	th.AssertNoErr(t, err)
+	th.CheckEquals(t, expected, actual)
+}
diff --git a/_site/openstack/compute/v2/images/results.go b/_site/openstack/compute/v2/images/results.go
new file mode 100644
index 0000000..3c22eeb
--- /dev/null
+++ b/_site/openstack/compute/v2/images/results.go
@@ -0,0 +1,90 @@
+package images
+
+import (
+	"github.com/mitchellh/mapstructure"
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/pagination"
+)
+
+// GetResult temporarily stores a Get response.
+type GetResult struct {
+	gophercloud.CommonResult
+}
+
+// Extract interprets a GetResult as an Image.
+func (gr GetResult) Extract() (*Image, error) {
+	if gr.Err != nil {
+		return nil, gr.Err
+	}
+
+	var decoded struct {
+		Image Image `mapstructure:"image"`
+	}
+
+	err := mapstructure.Decode(gr.Resp, &decoded)
+	return &decoded.Image, err
+}
+
+// Image is used for JSON (un)marshalling.
+// It provides a description of an OS image.
+type Image struct {
+	// ID contains the image's unique identifier.
+	ID string
+
+	Created string
+
+	// MinDisk and MinRAM specify the minimum resources a server must provide to be able to install the image.
+	MinDisk int
+	MinRAM  int
+
+	// Name provides a human-readable moniker for the OS image.
+	Name string
+
+	// The Progress and Status fields indicate image-creation status.
+	// Any usable image will have 100% progress.
+	Progress int
+	Status   string
+
+	Updated string
+}
+
+// ImagePage contains a single page of results from a List operation.
+// Use ExtractImages to convert it into a slice of usable structs.
+type ImagePage struct {
+	pagination.LinkedPageBase
+}
+
+// IsEmpty returns true if a page contains no Image results.
+func (page ImagePage) IsEmpty() (bool, error) {
+	images, err := ExtractImages(page)
+	if err != nil {
+		return true, err
+	}
+	return len(images) == 0, nil
+}
+
+// NextPageURL uses the response's embedded link reference to navigate to the next page of results.
+func (page ImagePage) NextPageURL() (string, error) {
+	type resp struct {
+		Links []gophercloud.Link `mapstructure:"images_links"`
+	}
+
+	var r resp
+	err := mapstructure.Decode(page.Body, &r)
+	if err != nil {
+		return "", err
+	}
+
+	return gophercloud.ExtractNextURL(r.Links)
+}
+
+// ExtractImages converts a page of List results into a slice of usable Image structs.
+func ExtractImages(page pagination.Page) ([]Image, error) {
+	casted := page.(ImagePage).Body
+	var results struct {
+		Images []Image `mapstructure:"images"`
+	}
+
+	err := mapstructure.Decode(casted, &results)
+	return results.Images, err
+}
diff --git a/_site/openstack/compute/v2/images/urls.go b/_site/openstack/compute/v2/images/urls.go
new file mode 100644
index 0000000..9b3c86d
--- /dev/null
+++ b/_site/openstack/compute/v2/images/urls.go
@@ -0,0 +1,11 @@
+package images
+
+import "github.com/rackspace/gophercloud"
+
+func listDetailURL(client *gophercloud.ServiceClient) string {
+	return client.ServiceURL("images", "detail")
+}
+
+func getURL(client *gophercloud.ServiceClient, id string) string {
+	return client.ServiceURL("images", id)
+}
diff --git a/_site/openstack/compute/v2/images/urls_test.go b/_site/openstack/compute/v2/images/urls_test.go
new file mode 100644
index 0000000..b1ab3d6
--- /dev/null
+++ b/_site/openstack/compute/v2/images/urls_test.go
@@ -0,0 +1,26 @@
+package images
+
+import (
+	"testing"
+
+	"github.com/rackspace/gophercloud"
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+const endpoint = "http://localhost:57909/"
+
+func endpointClient() *gophercloud.ServiceClient {
+	return &gophercloud.ServiceClient{Endpoint: endpoint}
+}
+
+func TestGetURL(t *testing.T) {
+	actual := getURL(endpointClient(), "foo")
+	expected := endpoint + "images/foo"
+	th.CheckEquals(t, expected, actual)
+}
+
+func TestListDetailURL(t *testing.T) {
+	actual := listDetailURL(endpointClient())
+	expected := endpoint + "images/detail"
+	th.CheckEquals(t, expected, actual)
+}
diff --git a/_site/openstack/compute/v2/servers/data_test.go b/_site/openstack/compute/v2/servers/data_test.go
new file mode 100644
index 0000000..d3a0ee0
--- /dev/null
+++ b/_site/openstack/compute/v2/servers/data_test.go
@@ -0,0 +1,328 @@
+package servers
+
+// Recorded responses for the server resource.
+
+const (
+	serverListBody = `
+      {
+        "servers": [
+          {
+            "status": "ACTIVE",
+            "updated": "2014-09-25T13:10:10Z",
+            "hostId": "29d3c8c896a45aa4c34e52247875d7fefc3d94bbcc9f622b5d204362",
+            "OS-EXT-SRV-ATTR:host": "devstack",
+            "addresses": {
+              "private": [
+                {
+                  "OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:7c:1b:2b",
+                  "version": 4,
+                  "addr": "10.0.0.32",
+                  "OS-EXT-IPS:type": "fixed"
+                }
+              ]
+            },
+            "links": [
+              {
+                "href": "http://104.130.131.164:8774/v2/fcad67a6189847c4aecfa3c81a05783b/servers/ef079b0c-e610-4dfb-b1aa-b49f07ac48e5",
+                "rel": "self"
+              },
+              {
+                "href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/servers/ef079b0c-e610-4dfb-b1aa-b49f07ac48e5",
+                "rel": "bookmark"
+              }
+            ],
+            "key_name": null,
+            "image": {
+              "id": "f90f6034-2570-4974-8351-6b49732ef2eb",
+              "links": [
+                {
+                  "href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/images/f90f6034-2570-4974-8351-6b49732ef2eb",
+                  "rel": "bookmark"
+                }
+              ]
+            },
+            "OS-EXT-STS:task_state": null,
+            "OS-EXT-STS:vm_state": "active",
+            "OS-EXT-SRV-ATTR:instance_name": "instance-0000001e",
+            "OS-SRV-USG:launched_at": "2014-09-25T13:10:10.000000",
+            "OS-EXT-SRV-ATTR:hypervisor_hostname": "devstack",
+            "flavor": {
+              "id": "1",
+              "links": [
+                {
+                  "href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/flavors/1",
+                  "rel": "bookmark"
+                }
+              ]
+            },
+            "id": "ef079b0c-e610-4dfb-b1aa-b49f07ac48e5",
+            "security_groups": [
+              {
+                "name": "default"
+              }
+            ],
+            "OS-SRV-USG:terminated_at": null,
+            "OS-EXT-AZ:availability_zone": "nova",
+            "user_id": "9349aff8be7545ac9d2f1d00999a23cd",
+            "name": "herp",
+            "created": "2014-09-25T13:10:02Z",
+            "tenant_id": "fcad67a6189847c4aecfa3c81a05783b",
+            "OS-DCF:diskConfig": "MANUAL",
+            "os-extended-volumes:volumes_attached": [],
+            "accessIPv4": "",
+            "accessIPv6": "",
+            "progress": 0,
+            "OS-EXT-STS:power_state": 1,
+            "config_drive": "",
+            "metadata": {}
+          },
+          {
+            "status": "ACTIVE",
+            "updated": "2014-09-25T13:04:49Z",
+            "hostId": "29d3c8c896a45aa4c34e52247875d7fefc3d94bbcc9f622b5d204362",
+            "OS-EXT-SRV-ATTR:host": "devstack",
+            "addresses": {
+              "private": [
+                {
+                  "OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:9e:89:be",
+                  "version": 4,
+                  "addr": "10.0.0.31",
+                  "OS-EXT-IPS:type": "fixed"
+                }
+              ]
+            },
+            "links": [
+              {
+                "href": "http://104.130.131.164:8774/v2/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba",
+                "rel": "self"
+              },
+              {
+                "href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba",
+                "rel": "bookmark"
+              }
+            ],
+            "key_name": null,
+            "image": {
+              "id": "f90f6034-2570-4974-8351-6b49732ef2eb",
+              "links": [
+                {
+                  "href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/images/f90f6034-2570-4974-8351-6b49732ef2eb",
+                  "rel": "bookmark"
+                }
+              ]
+            },
+            "OS-EXT-STS:task_state": null,
+            "OS-EXT-STS:vm_state": "active",
+            "OS-EXT-SRV-ATTR:instance_name": "instance-0000001d",
+            "OS-SRV-USG:launched_at": "2014-09-25T13:04:49.000000",
+            "OS-EXT-SRV-ATTR:hypervisor_hostname": "devstack",
+            "flavor": {
+              "id": "1",
+              "links": [
+                {
+                  "href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/flavors/1",
+                  "rel": "bookmark"
+                }
+              ]
+            },
+            "id": "9e5476bd-a4ec-4653-93d6-72c93aa682ba",
+            "security_groups": [
+              {
+                "name": "default"
+              }
+            ],
+            "OS-SRV-USG:terminated_at": null,
+            "OS-EXT-AZ:availability_zone": "nova",
+            "user_id": "9349aff8be7545ac9d2f1d00999a23cd",
+            "name": "derp",
+            "created": "2014-09-25T13:04:41Z",
+            "tenant_id": "fcad67a6189847c4aecfa3c81a05783b",
+            "OS-DCF:diskConfig": "MANUAL",
+            "os-extended-volumes:volumes_attached": [],
+            "accessIPv4": "",
+            "accessIPv6": "",
+            "progress": 0,
+            "OS-EXT-STS:power_state": 1,
+            "config_drive": "",
+            "metadata": {}
+          }
+        ]
+      }
+    `
+
+	singleServerBody = `
+    {
+      "server": {
+        "status": "ACTIVE",
+        "updated": "2014-09-25T13:04:49Z",
+        "hostId": "29d3c8c896a45aa4c34e52247875d7fefc3d94bbcc9f622b5d204362",
+        "OS-EXT-SRV-ATTR:host": "devstack",
+        "addresses": {
+          "private": [
+            {
+              "OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:9e:89:be",
+              "version": 4,
+              "addr": "10.0.0.31",
+              "OS-EXT-IPS:type": "fixed"
+            }
+          ]
+        },
+        "links": [
+          {
+            "href": "http://104.130.131.164:8774/v2/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba",
+            "rel": "self"
+          },
+          {
+            "href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba",
+            "rel": "bookmark"
+          }
+        ],
+        "key_name": null,
+        "image": {
+          "id": "f90f6034-2570-4974-8351-6b49732ef2eb",
+          "links": [
+            {
+              "href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/images/f90f6034-2570-4974-8351-6b49732ef2eb",
+              "rel": "bookmark"
+            }
+          ]
+        },
+        "OS-EXT-STS:task_state": null,
+        "OS-EXT-STS:vm_state": "active",
+        "OS-EXT-SRV-ATTR:instance_name": "instance-0000001d",
+        "OS-SRV-USG:launched_at": "2014-09-25T13:04:49.000000",
+        "OS-EXT-SRV-ATTR:hypervisor_hostname": "devstack",
+        "flavor": {
+          "id": "1",
+          "links": [
+            {
+              "href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/flavors/1",
+              "rel": "bookmark"
+            }
+          ]
+        },
+        "id": "9e5476bd-a4ec-4653-93d6-72c93aa682ba",
+        "security_groups": [
+          {
+            "name": "default"
+          }
+        ],
+        "OS-SRV-USG:terminated_at": null,
+        "OS-EXT-AZ:availability_zone": "nova",
+        "user_id": "9349aff8be7545ac9d2f1d00999a23cd",
+        "name": "derp",
+        "created": "2014-09-25T13:04:41Z",
+        "tenant_id": "fcad67a6189847c4aecfa3c81a05783b",
+        "OS-DCF:diskConfig": "MANUAL",
+        "os-extended-volumes:volumes_attached": [],
+        "accessIPv4": "",
+        "accessIPv6": "",
+        "progress": 0,
+        "OS-EXT-STS:power_state": 1,
+        "config_drive": "",
+        "metadata": {}
+      }
+    }
+    `
+)
+
+var (
+	serverHerp = Server{
+		Status:  "ACTIVE",
+		Updated: "2014-09-25T13:10:10Z",
+		HostID:  "29d3c8c896a45aa4c34e52247875d7fefc3d94bbcc9f622b5d204362",
+		Addresses: map[string]interface{}{
+			"private": []interface{}{
+				map[string]interface{}{
+					"OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:7c:1b:2b",
+					"version":                 float64(4),
+					"addr":                    "10.0.0.32",
+					"OS-EXT-IPS:type":         "fixed",
+				},
+			},
+		},
+		Links: []interface{}{
+			map[string]interface{}{
+				"href": "http://104.130.131.164:8774/v2/fcad67a6189847c4aecfa3c81a05783b/servers/ef079b0c-e610-4dfb-b1aa-b49f07ac48e5",
+				"rel":  "self",
+			},
+			map[string]interface{}{
+				"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/servers/ef079b0c-e610-4dfb-b1aa-b49f07ac48e5",
+				"rel":  "bookmark",
+			},
+		},
+		Image: map[string]interface{}{
+			"id": "f90f6034-2570-4974-8351-6b49732ef2eb",
+			"links": []interface{}{
+				map[string]interface{}{
+					"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/images/f90f6034-2570-4974-8351-6b49732ef2eb",
+					"rel":  "bookmark",
+				},
+			},
+		},
+		Flavor: map[string]interface{}{
+			"id": "1",
+			"links": []interface{}{
+				map[string]interface{}{
+					"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/flavors/1",
+					"rel":  "bookmark",
+				},
+			},
+		},
+		ID:       "ef079b0c-e610-4dfb-b1aa-b49f07ac48e5",
+		UserID:   "9349aff8be7545ac9d2f1d00999a23cd",
+		Name:     "herp",
+		Created:  "2014-09-25T13:10:02Z",
+		TenantID: "fcad67a6189847c4aecfa3c81a05783b",
+		Metadata: map[string]interface{}{},
+	}
+	serverDerp = Server{
+		Status:  "ACTIVE",
+		Updated: "2014-09-25T13:04:49Z",
+		HostID:  "29d3c8c896a45aa4c34e52247875d7fefc3d94bbcc9f622b5d204362",
+		Addresses: map[string]interface{}{
+			"private": []interface{}{
+				map[string]interface{}{
+					"OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:9e:89:be",
+					"version":                 float64(4),
+					"addr":                    "10.0.0.31",
+					"OS-EXT-IPS:type":         "fixed",
+				},
+			},
+		},
+		Links: []interface{}{
+			map[string]interface{}{
+				"href": "http://104.130.131.164:8774/v2/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba",
+				"rel":  "self",
+			},
+			map[string]interface{}{
+				"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba",
+				"rel":  "bookmark",
+			},
+		},
+		Image: map[string]interface{}{
+			"id": "f90f6034-2570-4974-8351-6b49732ef2eb",
+			"links": []interface{}{
+				map[string]interface{}{
+					"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/images/f90f6034-2570-4974-8351-6b49732ef2eb",
+					"rel":  "bookmark",
+				},
+			},
+		},
+		Flavor: map[string]interface{}{
+			"id": "1",
+			"links": []interface{}{
+				map[string]interface{}{
+					"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/flavors/1",
+					"rel":  "bookmark",
+				},
+			},
+		},
+		ID:       "9e5476bd-a4ec-4653-93d6-72c93aa682ba",
+		UserID:   "9349aff8be7545ac9d2f1d00999a23cd",
+		Name:     "derp",
+		Created:  "2014-09-25T13:04:41Z",
+		TenantID: "fcad67a6189847c4aecfa3c81a05783b",
+		Metadata: map[string]interface{}{},
+	}
+)
diff --git a/_site/openstack/compute/v2/servers/doc.go b/_site/openstack/compute/v2/servers/doc.go
new file mode 100644
index 0000000..0a1791d
--- /dev/null
+++ b/_site/openstack/compute/v2/servers/doc.go
@@ -0,0 +1,3 @@
+// Package servers provides convenient access to standard, OpenStack-defined
+// compute services.
+package servers
diff --git a/_site/openstack/compute/v2/servers/requests.go b/_site/openstack/compute/v2/servers/requests.go
new file mode 100644
index 0000000..df622bf
--- /dev/null
+++ b/_site/openstack/compute/v2/servers/requests.go
@@ -0,0 +1,489 @@
+package servers
+
+import (
+	"encoding/base64"
+	"fmt"
+
+	"github.com/racker/perigee"
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/pagination"
+)
+
+// ListOptsBuilder allows extensions to add additional parameters to the
+// List request.
+type ListOptsBuilder interface {
+	ToServerListQuery() (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 server attributes you want to see returned. Marker and Limit are used
+// for pagination.
+type ListOpts struct {
+	// A time/date stamp for when the server last changed status.
+	ChangesSince string `q:"changes-since"`
+
+	// Name of the image in URL format.
+	Image string `q:"image"`
+
+	// Name of the flavor in URL format.
+	Flavor string `q:"flavor"`
+
+	// Name of the server as a string; can be queried with regular expressions.
+	// Realize that ?name=bob returns both bob and bobb. If you need to match bob
+	// only, you can use a regular expression matching the syntax of the
+	// underlying database server implemented for Compute.
+	Name string `q:"name"`
+
+	// Value of the status of the server so that you can filter on "ACTIVE" for example.
+	Status string `q:"status"`
+
+	// Name of the host as a string.
+	Host string `q:"host"`
+
+	// UUID of the server at which you want to set a marker.
+	Marker string `q:"marker"`
+
+	// Integer value for the limit of values to return.
+	Limit int `q:"limit"`
+}
+
+// ToServerListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToServerListQuery() (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 servers accessible to you.
+func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
+	url := listDetailURL(client)
+
+	if opts != nil {
+		query, err := opts.ToServerListQuery()
+		if err != nil {
+			return pagination.Pager{Err: err}
+		}
+		url += query
+	}
+
+	createPageFn := func(r pagination.LastHTTPResponse) pagination.Page {
+		return ServerPage{pagination.LinkedPageBase{LastHTTPResponse: r}}
+	}
+
+	return pagination.NewPager(client, url, createPageFn)
+}
+
+// CreateOptsBuilder describes struct types that can be accepted by the Create call.
+// The CreateOpts struct in this package does.
+type CreateOptsBuilder interface {
+	ToServerCreateMap() map[string]interface{}
+}
+
+// Network is used within CreateOpts to control a new server's network attachments.
+type Network struct {
+	// UUID of a nova-network to attach to the newly provisioned server.
+	// Required unless Port is provided.
+	UUID string
+
+	// Port of a neutron network to attach to the newly provisioned server.
+	// Required unless UUID is provided.
+	Port string
+
+	// FixedIP [optional] specifies a fixed IPv4 address to be used on this network.
+	FixedIP string
+}
+
+// CreateOpts specifies server creation parameters.
+type CreateOpts struct {
+	// Name [required] is the name to assign to the newly launched server.
+	Name string
+
+	// ImageRef [required] is the ID or full URL to the image that contains the server's OS and initial state.
+	// Optional if using the boot-from-volume extension.
+	ImageRef string
+
+	// FlavorRef [required] is the ID or full URL to the flavor that describes the server's specs.
+	FlavorRef string
+
+	// SecurityGroups [optional] lists the names of the security groups to which this server should belong.
+	SecurityGroups []string
+
+	// UserData [optional] contains configuration information or scripts to use upon launch.
+	// Create will base64-encode it for you.
+	UserData []byte
+
+	// AvailabilityZone [optional] in which to launch the server.
+	AvailabilityZone string
+
+	// Networks [optional] dictates how this server will be attached to available networks.
+	// By default, the server will be attached to all isolated networks for the tenant.
+	Networks []Network
+
+	// Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the server.
+	Metadata map[string]string
+
+	// Personality [optional] includes the path and contents of a file to inject into the server at launch.
+	// The maximum size of the file is 255 bytes (decoded).
+	Personality []byte
+
+	// ConfigDrive [optional] enables metadata injection through a configuration drive.
+	ConfigDrive bool
+}
+
+// ToServerCreateMap assembles a request body based on the contents of a CreateOpts.
+func (opts CreateOpts) ToServerCreateMap() map[string]interface{} {
+	server := make(map[string]interface{})
+
+	server["name"] = opts.Name
+	server["imageRef"] = opts.ImageRef
+	server["flavorRef"] = opts.FlavorRef
+
+	if opts.UserData != nil {
+		encoded := base64.StdEncoding.EncodeToString(opts.UserData)
+		server["user_data"] = &encoded
+	}
+	if opts.Personality != nil {
+		encoded := base64.StdEncoding.EncodeToString(opts.Personality)
+		server["personality"] = &encoded
+	}
+	if opts.ConfigDrive {
+		server["config_drive"] = "true"
+	}
+	if opts.AvailabilityZone != "" {
+		server["availability_zone"] = opts.AvailabilityZone
+	}
+	if opts.Metadata != nil {
+		server["metadata"] = opts.Metadata
+	}
+
+	if len(opts.SecurityGroups) > 0 {
+		securityGroups := make([]map[string]interface{}, len(opts.SecurityGroups))
+		for i, groupName := range opts.SecurityGroups {
+			securityGroups[i] = map[string]interface{}{"name": groupName}
+		}
+	}
+	if len(opts.Networks) > 0 {
+		networks := make([]map[string]interface{}, len(opts.Networks))
+		for i, net := range opts.Networks {
+			networks[i] = make(map[string]interface{})
+			if net.UUID != "" {
+				networks[i]["uuid"] = net.UUID
+			}
+			if net.Port != "" {
+				networks[i]["port"] = net.Port
+			}
+			if net.FixedIP != "" {
+				networks[i]["fixed_ip"] = net.FixedIP
+			}
+		}
+	}
+
+	return map[string]interface{}{"server": server}
+}
+
+// Create requests a server to be provisioned to the user in the current tenant.
+func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
+	var result CreateResult
+	_, result.Err = perigee.Request("POST", listURL(client), perigee.Options{
+		Results:     &result.Resp,
+		ReqBody:     opts.ToServerCreateMap(),
+		MoreHeaders: client.Provider.AuthenticatedHeaders(),
+		OkCodes:     []int{202},
+	})
+	return result
+}
+
+// Delete requests that a server previously provisioned be removed from your account.
+func Delete(client *gophercloud.ServiceClient, id string) error {
+	_, err := perigee.Request("DELETE", deleteURL(client, id), perigee.Options{
+		MoreHeaders: client.Provider.AuthenticatedHeaders(),
+		OkCodes:     []int{204},
+	})
+	return err
+}
+
+// Get requests details on a single server, by ID.
+func Get(client *gophercloud.ServiceClient, id string) GetResult {
+	var result GetResult
+	_, result.Err = perigee.Request("GET", getURL(client, id), perigee.Options{
+		Results:     &result.Resp,
+		MoreHeaders: client.Provider.AuthenticatedHeaders(),
+	})
+	return result
+}
+
+// UpdateOptsBuilder allows extentions to add additional attributes to the Update request.
+type UpdateOptsBuilder interface {
+	ToServerUpdateMap() map[string]interface{}
+}
+
+// UpdateOpts specifies the base attributes that may be updated on an existing server.
+type UpdateOpts struct {
+	// Name [optional] changes the displayed name of the server.
+	// The server host name will *not* change.
+	// Server names are not constrained to be unique, even within the same tenant.
+	Name string
+
+	// AccessIPv4 [optional] provides a new IPv4 address for the instance.
+	AccessIPv4 string
+
+	// AccessIPv6 [optional] provides a new IPv6 address for the instance.
+	AccessIPv6 string
+}
+
+// ToServerUpdateMap formats an UpdateOpts structure into a request body.
+func (opts UpdateOpts) ToServerUpdateMap() map[string]interface{} {
+	server := make(map[string]string)
+	if opts.Name != "" {
+		server["name"] = opts.Name
+	}
+	if opts.AccessIPv4 != "" {
+		server["accessIPv4"] = opts.AccessIPv4
+	}
+	if opts.AccessIPv6 != "" {
+		server["accessIPv6"] = opts.AccessIPv6
+	}
+	return map[string]interface{}{"server": server}
+}
+
+// Update requests that various attributes of the indicated server be changed.
+func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
+	var result UpdateResult
+	_, result.Err = perigee.Request("PUT", updateURL(client, id), perigee.Options{
+		Results:     &result.Resp,
+		ReqBody:     opts.ToServerUpdateMap(),
+		MoreHeaders: client.Provider.AuthenticatedHeaders(),
+	})
+	return result
+}
+
+// ChangeAdminPassword alters the administrator or root password for a specified server.
+func ChangeAdminPassword(client *gophercloud.ServiceClient, id, newPassword string) error {
+	var req struct {
+		ChangePassword struct {
+			AdminPass string `json:"adminPass"`
+		} `json:"changePassword"`
+	}
+
+	req.ChangePassword.AdminPass = newPassword
+
+	_, err := perigee.Request("POST", actionURL(client, id), perigee.Options{
+		ReqBody:     req,
+		MoreHeaders: client.Provider.AuthenticatedHeaders(),
+		OkCodes:     []int{202},
+	})
+	return err
+}
+
+// ErrArgument errors occur when an argument supplied to a package function
+// fails to fall within acceptable values.  For example, the Reboot() function
+// expects the "how" parameter to be one of HardReboot or SoftReboot.  These
+// constants are (currently) strings, leading someone to wonder if they can pass
+// other string values instead, perhaps in an effort to break the API of their
+// provider.  Reboot() returns this error in this situation.
+//
+// Function identifies which function was called/which function is generating
+// the error.
+// Argument identifies which formal argument was responsible for producing the
+// error.
+// Value provides the value as it was passed into the function.
+type ErrArgument struct {
+	Function, Argument string
+	Value              interface{}
+}
+
+// Error yields a useful diagnostic for debugging purposes.
+func (e *ErrArgument) Error() string {
+	return fmt.Sprintf("Bad argument in call to %s, formal parameter %s, value %#v", e.Function, e.Argument, e.Value)
+}
+
+func (e *ErrArgument) String() string {
+	return e.Error()
+}
+
+// RebootMethod describes the mechanisms by which a server reboot can be requested.
+type RebootMethod string
+
+// These constants determine how a server should be rebooted.
+// See the Reboot() function for further details.
+const (
+	SoftReboot RebootMethod = "SOFT"
+	HardReboot RebootMethod = "HARD"
+	OSReboot                = SoftReboot
+	PowerCycle              = HardReboot
+)
+
+// Reboot requests that a given server reboot.
+// Two methods exist for rebooting a server:
+//
+// HardReboot (aka PowerCycle) restarts the server instance by physically cutting power to the machine, or if a VM,
+// terminating it at the hypervisor level.
+// It's done. Caput. Full stop.
+// Then, after a brief while, power is restored or the VM instance restarted.
+//
+// SoftReboot (aka OSReboot) simply tells the OS to restart under its own procedures.
+// E.g., in Linux, asking it to enter runlevel 6, or executing "sudo shutdown -r now", or by asking Windows to restart the machine.
+func Reboot(client *gophercloud.ServiceClient, id string, how RebootMethod) error {
+	if (how != SoftReboot) && (how != HardReboot) {
+		return &ErrArgument{
+			Function: "Reboot",
+			Argument: "how",
+			Value:    how,
+		}
+	}
+
+	_, err := perigee.Request("POST", actionURL(client, id), perigee.Options{
+		ReqBody: struct {
+			C map[string]string `json:"reboot"`
+		}{
+			map[string]string{"type": string(how)},
+		},
+		MoreHeaders: client.Provider.AuthenticatedHeaders(),
+		OkCodes:     []int{202},
+	})
+	return err
+}
+
+// RebuildOptsBuilder is an interface that allows extensions to override the
+// default behaviour of rebuild options
+type RebuildOptsBuilder interface {
+	ToServerRebuildMap() (map[string]interface{}, error)
+}
+
+// RebuildOpts represents the configuration options used in a server rebuild
+// operation
+type RebuildOpts struct {
+	// Required. The ID of the image you want your server to be provisioned on
+	ImageID string
+
+	// Name to set the server to
+	Name string
+
+	// Required. The server's admin password
+	AdminPass string
+
+	// AccessIPv4 [optional] provides a new IPv4 address for the instance.
+	AccessIPv4 string
+
+	// AccessIPv6 [optional] provides a new IPv6 address for the instance.
+	AccessIPv6 string
+
+	// Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the server.
+	Metadata map[string]string
+
+	// Personality [optional] includes the path and contents of a file to inject into the server at launch.
+	// The maximum size of the file is 255 bytes (decoded).
+	Personality []byte
+}
+
+// ToServerRebuildMap formats a RebuildOpts struct into a map for use in JSON
+func (opts RebuildOpts) ToServerRebuildMap() (map[string]interface{}, error) {
+	var err error
+	server := make(map[string]interface{})
+
+	if opts.AdminPass == "" {
+		err = fmt.Errorf("AdminPass is required")
+	}
+
+	if opts.ImageID == "" {
+		err = fmt.Errorf("ImageID is required")
+	}
+
+	if err != nil {
+		return server, err
+	}
+
+	server["name"] = opts.Name
+	server["adminPass"] = opts.AdminPass
+	server["imageRef"] = opts.ImageID
+
+	if opts.AccessIPv4 != "" {
+		server["accessIPv4"] = opts.AccessIPv4
+	}
+
+	if opts.AccessIPv6 != "" {
+		server["accessIPv6"] = opts.AccessIPv6
+	}
+
+	if opts.Metadata != nil {
+		server["metadata"] = opts.Metadata
+	}
+
+	if opts.Personality != nil {
+		encoded := base64.StdEncoding.EncodeToString(opts.Personality)
+		server["personality"] = &encoded
+	}
+
+	return map[string]interface{}{"rebuild": server}, nil
+}
+
+// Rebuild will reprovision the server according to the configuration options
+// provided in the RebuildOpts struct.
+func Rebuild(client *gophercloud.ServiceClient, id string, opts RebuildOptsBuilder) RebuildResult {
+	var result RebuildResult
+
+	if id == "" {
+		result.Err = fmt.Errorf("ID is required")
+		return result
+	}
+
+	reqBody, err := opts.ToServerRebuildMap()
+	if err != nil {
+		result.Err = err
+		return result
+	}
+
+	_, result.Err = perigee.Request("POST", actionURL(client, id), perigee.Options{
+		ReqBody:     &reqBody,
+		Results:     &result.Resp,
+		MoreHeaders: client.Provider.AuthenticatedHeaders(),
+		OkCodes:     []int{202},
+	})
+
+	return result
+}
+
+// Resize instructs the provider to change the flavor of the server.
+// Note that this implies rebuilding it.
+// Unfortunately, one cannot pass rebuild parameters to the resize function.
+// When the resize completes, the server will be in RESIZE_VERIFY state.
+// While in this state, you can explore the use of the new server's configuration.
+// If you like it, call ConfirmResize() to commit the resize permanently.
+// Otherwise, call RevertResize() to restore the old configuration.
+func Resize(client *gophercloud.ServiceClient, id, flavorRef string) error {
+	_, err := perigee.Request("POST", actionURL(client, id), perigee.Options{
+		ReqBody: struct {
+			R map[string]interface{} `json:"resize"`
+		}{
+			map[string]interface{}{"flavorRef": flavorRef},
+		},
+		MoreHeaders: client.Provider.AuthenticatedHeaders(),
+		OkCodes:     []int{202},
+	})
+	return err
+}
+
+// ConfirmResize confirms a previous resize operation on a server.
+// See Resize() for more details.
+func ConfirmResize(client *gophercloud.ServiceClient, id string) error {
+	_, err := perigee.Request("POST", actionURL(client, id), perigee.Options{
+		ReqBody:     map[string]interface{}{"confirmResize": nil},
+		MoreHeaders: client.Provider.AuthenticatedHeaders(),
+		OkCodes:     []int{204},
+	})
+	return err
+}
+
+// RevertResize cancels a previous resize operation on a server.
+// See Resize() for more details.
+func RevertResize(client *gophercloud.ServiceClient, id string) error {
+	_, err := perigee.Request("POST", actionURL(client, id), perigee.Options{
+		ReqBody:     map[string]interface{}{"revertResize": nil},
+		MoreHeaders: client.Provider.AuthenticatedHeaders(),
+		OkCodes:     []int{202},
+	})
+	return err
+}
diff --git a/_site/openstack/compute/v2/servers/requests_test.go b/_site/openstack/compute/v2/servers/requests_test.go
new file mode 100644
index 0000000..86fe1e2
--- /dev/null
+++ b/_site/openstack/compute/v2/servers/requests_test.go
@@ -0,0 +1,283 @@
+package servers
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+
+	"github.com/rackspace/gophercloud/pagination"
+	"github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func TestListServers(t *testing.T) {
+	testhelper.SetupHTTP()
+	defer testhelper.TeardownHTTP()
+
+	testhelper.Mux.HandleFunc("/servers/detail", func(w http.ResponseWriter, r *http.Request) {
+		testhelper.TestMethod(t, r, "GET")
+		testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+		w.Header().Add("Content-Type", "application/json")
+		r.ParseForm()
+		marker := r.Form.Get("marker")
+		switch marker {
+		case "":
+			fmt.Fprintf(w, serverListBody)
+		case "9e5476bd-a4ec-4653-93d6-72c93aa682ba":
+			fmt.Fprintf(w, `{ "servers": [] }`)
+		default:
+			t.Fatalf("/servers/detail invoked with unexpected marker=[%s]", marker)
+		}
+	})
+
+	pages := 0
+	err := List(fake.ServiceClient(), ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
+		pages++
+
+		actual, err := ExtractServers(page)
+		if err != nil {
+			return false, err
+		}
+
+		if len(actual) != 2 {
+			t.Fatalf("Expected 2 servers, got %d", len(actual))
+		}
+		equalServers(t, serverHerp, actual[0])
+		equalServers(t, serverDerp, actual[1])
+
+		return true, nil
+	})
+
+	testhelper.AssertNoErr(t, err)
+
+	if pages != 1 {
+		t.Errorf("Expected 1 page, saw %d", pages)
+	}
+}
+
+func TestCreateServer(t *testing.T) {
+	testhelper.SetupHTTP()
+	defer testhelper.TeardownHTTP()
+
+	testhelper.Mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) {
+		testhelper.TestMethod(t, r, "POST")
+		testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		testhelper.TestJSONRequest(t, r, `{
+			"server": {
+				"name": "derp",
+				"imageRef": "f90f6034-2570-4974-8351-6b49732ef2eb",
+				"flavorRef": "1"
+			}
+		}`)
+
+		w.WriteHeader(http.StatusAccepted)
+		w.Header().Add("Content-Type", "application/json")
+		fmt.Fprintf(w, singleServerBody)
+	})
+
+	client := fake.ServiceClient()
+	actual, err := Create(client, CreateOpts{
+		Name:      "derp",
+		ImageRef:  "f90f6034-2570-4974-8351-6b49732ef2eb",
+		FlavorRef: "1",
+	}).Extract()
+	if err != nil {
+		t.Fatalf("Unexpected Create error: %v", err)
+	}
+
+	equalServers(t, serverDerp, *actual)
+}
+
+func TestDeleteServer(t *testing.T) {
+	testhelper.SetupHTTP()
+	defer testhelper.TeardownHTTP()
+
+	testhelper.Mux.HandleFunc("/servers/asdfasdfasdf", func(w http.ResponseWriter, r *http.Request) {
+		testhelper.TestMethod(t, r, "DELETE")
+		testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+		w.WriteHeader(http.StatusNoContent)
+	})
+
+	client := fake.ServiceClient()
+	err := Delete(client, "asdfasdfasdf")
+	if err != nil {
+		t.Fatalf("Unexpected Delete error: %v", err)
+	}
+}
+
+func TestGetServer(t *testing.T) {
+	testhelper.SetupHTTP()
+	defer testhelper.TeardownHTTP()
+
+	testhelper.Mux.HandleFunc("/servers/1234asdf", func(w http.ResponseWriter, r *http.Request) {
+		testhelper.TestMethod(t, r, "GET")
+		testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		testhelper.TestHeader(t, r, "Accept", "application/json")
+
+		fmt.Fprintf(w, singleServerBody)
+	})
+
+	client := fake.ServiceClient()
+	actual, err := Get(client, "1234asdf").Extract()
+	if err != nil {
+		t.Fatalf("Unexpected Get error: %v", err)
+	}
+
+	equalServers(t, serverDerp, *actual)
+}
+
+func TestUpdateServer(t *testing.T) {
+	testhelper.SetupHTTP()
+	defer testhelper.TeardownHTTP()
+
+	testhelper.Mux.HandleFunc("/servers/1234asdf", func(w http.ResponseWriter, r *http.Request) {
+		testhelper.TestMethod(t, r, "PUT")
+		testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		testhelper.TestHeader(t, r, "Accept", "application/json")
+		testhelper.TestHeader(t, r, "Content-Type", "application/json")
+		testhelper.TestJSONRequest(t, r, `{ "server": { "name": "new-name" } }`)
+
+		fmt.Fprintf(w, singleServerBody)
+	})
+
+	client := fake.ServiceClient()
+	actual, err := Update(client, "1234asdf", UpdateOpts{Name: "new-name"}).Extract()
+	if err != nil {
+		t.Fatalf("Unexpected Update error: %v", err)
+	}
+
+	equalServers(t, serverDerp, *actual)
+}
+
+func TestChangeServerAdminPassword(t *testing.T) {
+	testhelper.SetupHTTP()
+	defer testhelper.TeardownHTTP()
+
+	testhelper.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) {
+		testhelper.TestMethod(t, r, "POST")
+		testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		testhelper.TestJSONRequest(t, r, `{ "changePassword": { "adminPass": "new-password" } }`)
+
+		w.WriteHeader(http.StatusAccepted)
+	})
+
+	client := fake.ServiceClient()
+	err := ChangeAdminPassword(client, "1234asdf", "new-password")
+	if err != nil {
+		t.Errorf("Unexpected ChangeAdminPassword error: %v", err)
+	}
+}
+
+func TestRebootServer(t *testing.T) {
+	testhelper.SetupHTTP()
+	defer testhelper.TeardownHTTP()
+
+	testhelper.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) {
+		testhelper.TestMethod(t, r, "POST")
+		testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		testhelper.TestJSONRequest(t, r, `{ "reboot": { "type": "SOFT" } }`)
+
+		w.WriteHeader(http.StatusAccepted)
+	})
+
+	client := fake.ServiceClient()
+	err := Reboot(client, "1234asdf", SoftReboot)
+	if err != nil {
+		t.Errorf("Unexpected Reboot error: %v", err)
+	}
+}
+
+func TestRebuildServer(t *testing.T) {
+	testhelper.SetupHTTP()
+	defer testhelper.TeardownHTTP()
+
+	testhelper.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) {
+		testhelper.TestMethod(t, r, "POST")
+		testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		testhelper.TestJSONRequest(t, r, `
+			{
+				"rebuild": {
+					"name": "new-name",
+					"adminPass": "swordfish",
+					"imageRef": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/images/f90f6034-2570-4974-8351-6b49732ef2eb",
+					"accessIPv4": "1.2.3.4"
+				}
+			}
+		`)
+
+		w.WriteHeader(http.StatusAccepted)
+		w.Header().Add("Content-Type", "application/json")
+		fmt.Fprintf(w, singleServerBody)
+	})
+
+	opts := RebuildOpts{
+		Name:       "new-name",
+		AdminPass:  "swordfish",
+		ImageID:    "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/images/f90f6034-2570-4974-8351-6b49732ef2eb",
+		AccessIPv4: "1.2.3.4",
+	}
+
+	actual, err := Rebuild(serviceClient(), "1234asdf", opts).Extract()
+	testhelper.AssertNoErr(t, err)
+
+	equalServers(t, serverDerp, *actual)
+}
+
+func TestResizeServer(t *testing.T) {
+	testhelper.SetupHTTP()
+	defer testhelper.TeardownHTTP()
+
+	testhelper.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) {
+		testhelper.TestMethod(t, r, "POST")
+		testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		testhelper.TestJSONRequest(t, r, `{ "resize": { "flavorRef": "2" } }`)
+
+		w.WriteHeader(http.StatusAccepted)
+	})
+
+	client := fake.ServiceClient()
+	err := Resize(client, "1234asdf", "2")
+	if err != nil {
+		t.Errorf("Unexpected Reboot error: %v", err)
+	}
+}
+
+func TestConfirmResize(t *testing.T) {
+	testhelper.SetupHTTP()
+	defer testhelper.TeardownHTTP()
+
+	testhelper.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) {
+		testhelper.TestMethod(t, r, "POST")
+		testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		testhelper.TestJSONRequest(t, r, `{ "confirmResize": null }`)
+
+		w.WriteHeader(http.StatusNoContent)
+	})
+
+	client := fake.ServiceClient()
+	err := ConfirmResize(client, "1234asdf")
+	if err != nil {
+		t.Errorf("Unexpected ConfirmResize error: %v", err)
+	}
+}
+
+func TestRevertResize(t *testing.T) {
+	testhelper.SetupHTTP()
+	defer testhelper.TeardownHTTP()
+
+	testhelper.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) {
+		testhelper.TestMethod(t, r, "POST")
+		testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		testhelper.TestJSONRequest(t, r, `{ "revertResize": null }`)
+
+		w.WriteHeader(http.StatusAccepted)
+	})
+
+	client := fake.ServiceClient()
+	err := RevertResize(client, "1234asdf")
+	if err != nil {
+		t.Errorf("Unexpected RevertResize error: %v", err)
+	}
+}
diff --git a/_site/openstack/compute/v2/servers/results.go b/_site/openstack/compute/v2/servers/results.go
new file mode 100644
index 0000000..d284ed8
--- /dev/null
+++ b/_site/openstack/compute/v2/servers/results.go
@@ -0,0 +1,141 @@
+package servers
+
+import (
+	"github.com/mitchellh/mapstructure"
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/pagination"
+)
+
+type serverResult struct {
+	gophercloud.CommonResult
+}
+
+// Extract interprets any serverResult as a Server, if possible.
+func (r serverResult) Extract() (*Server, error) {
+	if r.Err != nil {
+		return nil, r.Err
+	}
+
+	var response struct {
+		Server Server `mapstructure:"server"`
+	}
+
+	err := mapstructure.Decode(r.Resp, &response)
+	return &response.Server, err
+}
+
+// CreateResult temporarily contains the response from a Create call.
+type CreateResult struct {
+	serverResult
+}
+
+// GetResult temporarily contains the response from a Get call.
+type GetResult struct {
+	serverResult
+}
+
+// UpdateResult temporarily contains the response from an Update call.
+type UpdateResult struct {
+	serverResult
+}
+
+// RebuildResult temporarily contains the response from a Rebuild call.
+type RebuildResult struct {
+	serverResult
+}
+
+// Server exposes only the standard OpenStack fields corresponding to a given server on the user's account.
+type Server struct {
+	// ID uniquely identifies this server amongst all other servers, including those not accessible to the current tenant.
+	ID string
+
+	// TenantID identifies the tenant owning this server resource.
+	TenantID string `mapstructure:"tenant_id"`
+
+	// UserID uniquely identifies the user account owning the tenant.
+	UserID string `mapstructure:"user_id"`
+
+	// Name contains the human-readable name for the server.
+	Name string
+
+	// Updated and Created contain ISO-8601 timestamps of when the state of the server last changed, and when it was created.
+	Updated string
+	Created string
+
+	HostID string
+
+	// Status contains the current operational status of the server, such as IN_PROGRESS or ACTIVE.
+	Status string
+
+	// Progress ranges from 0..100.
+	// A request made against the server completes only once Progress reaches 100.
+	Progress int
+
+	// AccessIPv4 and AccessIPv6 contain the IP addresses of the server, suitable for remote access for administration.
+	AccessIPv4 string
+	AccessIPv6 string
+
+	// Image refers to a JSON object, which itself indicates the OS image used to deploy the server.
+	Image map[string]interface{}
+
+	// Flavor refers to a JSON object, which itself indicates the hardware configuration of the deployed server.
+	Flavor map[string]interface{}
+
+	// Addresses includes a list of all IP addresses assigned to the server, keyed by pool.
+	Addresses map[string]interface{}
+
+	// Metadata includes a list of all user-specified key-value pairs attached to the server.
+	Metadata map[string]interface{}
+
+	// Links includes HTTP references to the itself, useful for passing along to other APIs that might want a server reference.
+	Links []interface{}
+
+	// KeyName indicates which public key was injected into the server on launch.
+	KeyName string `mapstructure:"keyname"`
+
+	// AdminPass will generally be empty ("").  However, it will contain the administrative password chosen when provisioning a new server without a set AdminPass setting in the first place.
+	// Note that this is the ONLY time this field will be valid.
+	AdminPass string `mapstructure:"adminPass"`
+}
+
+// ServerPage 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 ExtractServers call.
+type ServerPage struct {
+	pagination.LinkedPageBase
+}
+
+// IsEmpty returns true if a page contains no Server results.
+func (page ServerPage) IsEmpty() (bool, error) {
+	servers, err := ExtractServers(page)
+	if err != nil {
+		return true, err
+	}
+	return len(servers) == 0, nil
+}
+
+// NextPageURL uses the response's embedded link reference to navigate to the next page of results.
+func (page ServerPage) NextPageURL() (string, error) {
+	type resp struct {
+		Links []gophercloud.Link `mapstructure:"servers_links"`
+	}
+
+	var r resp
+	err := mapstructure.Decode(page.Body, &r)
+	if err != nil {
+		return "", err
+	}
+
+	return gophercloud.ExtractNextURL(r.Links)
+}
+
+// ExtractServers interprets the results of a single page from a List() call, producing a slice of Server entities.
+func ExtractServers(page pagination.Page) ([]Server, error) {
+	casted := page.(ServerPage).Body
+
+	var response struct {
+		Servers []Server `mapstructure:"servers"`
+	}
+	err := mapstructure.Decode(casted, &response)
+	return response.Servers, err
+}
diff --git a/_site/openstack/compute/v2/servers/servers_test.go b/_site/openstack/compute/v2/servers/servers_test.go
new file mode 100644
index 0000000..590fc8b
--- /dev/null
+++ b/_site/openstack/compute/v2/servers/servers_test.go
@@ -0,0 +1,65 @@
+package servers
+
+import (
+	"reflect"
+	"testing"
+)
+
+// This provides more fine-grained failures when Servers differ, because Server structs are too damn big to compare by eye.
+// FIXME I should absolutely refactor this into a general-purpose thing in testhelper.
+func equalServers(t *testing.T, expected Server, actual Server) {
+	if expected.ID != actual.ID {
+		t.Errorf("ID differs. expected=[%s], actual=[%s]", expected.ID, actual.ID)
+	}
+	if expected.TenantID != actual.TenantID {
+		t.Errorf("TenantID differs. expected=[%s], actual=[%s]", expected.TenantID, actual.TenantID)
+	}
+	if expected.UserID != actual.UserID {
+		t.Errorf("UserID differs. expected=[%s], actual=[%s]", expected.UserID, actual.UserID)
+	}
+	if expected.Name != actual.Name {
+		t.Errorf("Name differs. expected=[%s], actual=[%s]", expected.Name, actual.Name)
+	}
+	if expected.Updated != actual.Updated {
+		t.Errorf("Updated differs. expected=[%s], actual=[%s]", expected.Updated, actual.Updated)
+	}
+	if expected.Created != actual.Created {
+		t.Errorf("Created differs. expected=[%s], actual=[%s]", expected.Created, actual.Created)
+	}
+	if expected.HostID != actual.HostID {
+		t.Errorf("HostID differs. expected=[%s], actual=[%s]", expected.HostID, actual.HostID)
+	}
+	if expected.Status != actual.Status {
+		t.Errorf("Status differs. expected=[%s], actual=[%s]", expected.Status, actual.Status)
+	}
+	if expected.Progress != actual.Progress {
+		t.Errorf("Progress differs. expected=[%s], actual=[%s]", expected.Progress, actual.Progress)
+	}
+	if expected.AccessIPv4 != actual.AccessIPv4 {
+		t.Errorf("AccessIPv4 differs. expected=[%s], actual=[%s]", expected.AccessIPv4, actual.AccessIPv4)
+	}
+	if expected.AccessIPv6 != actual.AccessIPv6 {
+		t.Errorf("AccessIPv6 differs. expected=[%s], actual=[%s]", expected.AccessIPv6, actual.AccessIPv6)
+	}
+	if !reflect.DeepEqual(expected.Image, actual.Image) {
+		t.Errorf("Image differs. expected=[%s], actual=[%s]", expected.Image, actual.Image)
+	}
+	if !reflect.DeepEqual(expected.Flavor, actual.Flavor) {
+		t.Errorf("Flavor differs. expected=[%s], actual=[%s]", expected.Flavor, actual.Flavor)
+	}
+	if !reflect.DeepEqual(expected.Addresses, actual.Addresses) {
+		t.Errorf("Addresses differ. expected=[%s], actual=[%s]", expected.Addresses, actual.Addresses)
+	}
+	if !reflect.DeepEqual(expected.Metadata, actual.Metadata) {
+		t.Errorf("Metadata differs. expected=[%s], actual=[%s]", expected.Metadata, actual.Metadata)
+	}
+	if !reflect.DeepEqual(expected.Links, actual.Links) {
+		t.Errorf("Links differs. expected=[%s], actual=[%s]", expected.Links, actual.Links)
+	}
+	if expected.KeyName != actual.KeyName {
+		t.Errorf("KeyName differs. expected=[%s], actual=[%s]", expected.KeyName, actual.KeyName)
+	}
+	if expected.AdminPass != actual.AdminPass {
+		t.Errorf("AdminPass differs. expected=[%s], actual=[%s]", expected.AdminPass, actual.AdminPass)
+	}
+}
diff --git a/_site/openstack/compute/v2/servers/urls.go b/_site/openstack/compute/v2/servers/urls.go
new file mode 100644
index 0000000..57587ab
--- /dev/null
+++ b/_site/openstack/compute/v2/servers/urls.go
@@ -0,0 +1,31 @@
+package servers
+
+import "github.com/rackspace/gophercloud"
+
+func createURL(client *gophercloud.ServiceClient) string {
+	return client.ServiceURL("servers")
+}
+
+func listURL(client *gophercloud.ServiceClient) string {
+	return createURL(client)
+}
+
+func listDetailURL(client *gophercloud.ServiceClient) string {
+	return client.ServiceURL("servers", "detail")
+}
+
+func deleteURL(client *gophercloud.ServiceClient, id string) string {
+	return client.ServiceURL("servers", id)
+}
+
+func getURL(client *gophercloud.ServiceClient, id string) string {
+	return deleteURL(client, id)
+}
+
+func updateURL(client *gophercloud.ServiceClient, id string) string {
+	return deleteURL(client, id)
+}
+
+func actionURL(client *gophercloud.ServiceClient, id string) string {
+	return client.ServiceURL("servers", id, "action")
+}
diff --git a/_site/openstack/compute/v2/servers/urls_test.go b/_site/openstack/compute/v2/servers/urls_test.go
new file mode 100644
index 0000000..cc895c9
--- /dev/null
+++ b/_site/openstack/compute/v2/servers/urls_test.go
@@ -0,0 +1,56 @@
+package servers
+
+import (
+	"testing"
+
+	"github.com/rackspace/gophercloud"
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+const endpoint = "http://localhost:57909"
+
+func endpointClient() *gophercloud.ServiceClient {
+	return &gophercloud.ServiceClient{Endpoint: endpoint}
+}
+
+func TestCreateURL(t *testing.T) {
+	actual := createURL(endpointClient())
+	expected := endpoint + "servers"
+	th.CheckEquals(t, expected, actual)
+}
+
+func TestListURL(t *testing.T) {
+	actual := listURL(endpointClient())
+	expected := endpoint + "servers"
+	th.CheckEquals(t, expected, actual)
+}
+
+func TestListDetailURL(t *testing.T) {
+	actual := listDetailURL(endpointClient())
+	expected := endpoint + "servers/detail"
+	th.CheckEquals(t, expected, actual)
+}
+
+func TestDeleteURL(t *testing.T) {
+	actual := deleteURL(endpointClient(), "foo")
+	expected := endpoint + "servers/foo"
+	th.CheckEquals(t, expected, actual)
+}
+
+func TestGetURL(t *testing.T) {
+	actual := getURL(endpointClient(), "foo")
+	expected := endpoint + "servers/foo"
+	th.CheckEquals(t, expected, actual)
+}
+
+func TestUpdateURL(t *testing.T) {
+	actual := updateURL(endpointClient(), "foo")
+	expected := endpoint + "servers/foo"
+	th.CheckEquals(t, expected, actual)
+}
+
+func TestActionURL(t *testing.T) {
+	actual := actionURL(endpointClient(), "foo")
+	expected := endpoint + "servers/foo/action"
+	th.CheckEquals(t, expected, actual)
+}