move unit tests into 'testing' directories
diff --git a/openstack/compute/v2/servers/testing/doc.go b/openstack/compute/v2/servers/testing/doc.go
new file mode 100644
index 0000000..7603f83
--- /dev/null
+++ b/openstack/compute/v2/servers/testing/doc.go
@@ -0,0 +1 @@
+package testing
diff --git a/openstack/compute/v2/servers/testing/fixtures.go b/openstack/compute/v2/servers/testing/fixtures.go
new file mode 100644
index 0000000..b4fb7ff
--- /dev/null
+++ b/openstack/compute/v2/servers/testing/fixtures.go
@@ -0,0 +1,722 @@
+package testing
+
+import (
+ "fmt"
+ "net/http"
+ "testing"
+
+ "github.com/gophercloud/gophercloud"
+ "github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
+ th "github.com/gophercloud/gophercloud/testhelper"
+ "github.com/gophercloud/gophercloud/testhelper/client"
+)
+
+// ServerListBody contains the canned body of a servers.List response.
+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 is the canned body of a Get request on an existing server.
+const 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": {}
+ }
+}
+`
+
+const ServerPasswordBody = `
+{
+ "password": "xlozO3wLCBRWAa2yDjCCVx8vwNPypxnypmRYDa/zErlQ+EzPe1S/Gz6nfmC52mOlOSCRuUOmG7kqqgejPof6M7bOezS387zjq4LSvvwp28zUknzy4YzfFGhnHAdai3TxUJ26pfQCYrq8UTzmKF2Bq8ioSEtVVzM0A96pDh8W2i7BOz6MdoiVyiev/I1K2LsuipfxSJR7Wdke4zNXJjHHP2RfYsVbZ/k9ANu+Nz4iIH8/7Cacud/pphH7EjrY6a4RZNrjQskrhKYed0YERpotyjYk1eDtRe72GrSiXteqCM4biaQ5w3ruS+AcX//PXk3uJ5kC7d67fPXaVz4WaQRYMg=="
+}
+`
+
+var (
+ // ServerHerp is a Server struct that should correspond to the first result in ServerListBody.
+ ServerHerp = servers.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{}{},
+ SecurityGroups: []map[string]interface{}{
+ map[string]interface{}{
+ "name": "default",
+ },
+ },
+ }
+
+ // ServerDerp is a Server struct that should correspond to the second server in ServerListBody.
+ ServerDerp = servers.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{}{},
+ SecurityGroups: []map[string]interface{}{
+ map[string]interface{}{
+ "name": "default",
+ },
+ },
+ }
+)
+
+type CreateOptsWithCustomField struct {
+ servers.CreateOpts
+ Foo string `json:"foo,omitempty"`
+}
+
+func (opts CreateOptsWithCustomField) ToServerCreateMap() (map[string]interface{}, error) {
+ return gophercloud.BuildRequestBody(opts, "server")
+}
+
+// HandleServerCreationSuccessfully sets up the test server to respond to a server creation request
+// with a given response.
+func HandleServerCreationSuccessfully(t *testing.T, response string) {
+ th.Mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "POST")
+ th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+ th.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, response)
+ })
+}
+
+// HandleServerCreationWithCustomFieldSuccessfully sets up the test server to respond to a server creation request
+// with a given response.
+func HandleServerCreationWithCustomFieldSuccessfully(t *testing.T, response string) {
+ th.Mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "POST")
+ th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+ th.TestJSONRequest(t, r, `{
+ "server": {
+ "name": "derp",
+ "imageRef": "f90f6034-2570-4974-8351-6b49732ef2eb",
+ "flavorRef": "1",
+ "foo": "bar"
+ }
+ }`)
+
+ w.WriteHeader(http.StatusAccepted)
+ w.Header().Add("Content-Type", "application/json")
+ fmt.Fprintf(w, response)
+ })
+}
+
+// HandleServerListSuccessfully sets up the test server to respond to a server List request.
+func HandleServerListSuccessfully(t *testing.T) {
+ th.Mux.HandleFunc("/servers/detail", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", client.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)
+ }
+ })
+}
+
+// HandleServerDeletionSuccessfully sets up the test server to respond to a server deletion request.
+func HandleServerDeletionSuccessfully(t *testing.T) {
+ th.Mux.HandleFunc("/servers/asdfasdfasdf", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "DELETE")
+ th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+
+ w.WriteHeader(http.StatusNoContent)
+ })
+}
+
+// HandleServerForceDeletionSuccessfully sets up the test server to respond to a server force deletion
+// request.
+func HandleServerForceDeletionSuccessfully(t *testing.T) {
+ th.Mux.HandleFunc("/servers/asdfasdfasdf/action", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "POST")
+ th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+ th.TestJSONRequest(t, r, `{ "forceDelete": "" }`)
+
+ w.WriteHeader(http.StatusAccepted)
+ })
+}
+
+// HandleServerGetSuccessfully sets up the test server to respond to a server Get request.
+func HandleServerGetSuccessfully(t *testing.T) {
+ th.Mux.HandleFunc("/servers/1234asdf", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+ th.TestHeader(t, r, "Accept", "application/json")
+
+ fmt.Fprintf(w, SingleServerBody)
+ })
+}
+
+// HandleServerUpdateSuccessfully sets up the test server to respond to a server Update request.
+func HandleServerUpdateSuccessfully(t *testing.T) {
+ th.Mux.HandleFunc("/servers/1234asdf", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "PUT")
+ th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+ th.TestHeader(t, r, "Accept", "application/json")
+ th.TestHeader(t, r, "Content-Type", "application/json")
+ th.TestJSONRequest(t, r, `{ "server": { "name": "new-name" } }`)
+
+ fmt.Fprintf(w, SingleServerBody)
+ })
+}
+
+// HandleAdminPasswordChangeSuccessfully sets up the test server to respond to a server password
+// change request.
+func HandleAdminPasswordChangeSuccessfully(t *testing.T) {
+ th.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "POST")
+ th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+ th.TestJSONRequest(t, r, `{ "changePassword": { "adminPass": "new-password" } }`)
+
+ w.WriteHeader(http.StatusAccepted)
+ })
+}
+
+// HandleRebootSuccessfully sets up the test server to respond to a reboot request with success.
+func HandleRebootSuccessfully(t *testing.T) {
+ th.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "POST")
+ th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+ th.TestJSONRequest(t, r, `{ "reboot": { "type": "SOFT" } }`)
+
+ w.WriteHeader(http.StatusAccepted)
+ })
+}
+
+// HandleRebuildSuccessfully sets up the test server to respond to a rebuild request with success.
+func HandleRebuildSuccessfully(t *testing.T, response string) {
+ th.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "POST")
+ th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+ th.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, response)
+ })
+}
+
+// HandleServerRescueSuccessfully sets up the test server to respond to a server Rescue request.
+func HandleServerRescueSuccessfully(t *testing.T) {
+ th.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "POST")
+ th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+ th.TestJSONRequest(t, r, `{ "rescue": { "adminPass": "1234567890" } }`)
+
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(`{ "adminPass": "1234567890" }`))
+ })
+}
+
+// HandleMetadatumGetSuccessfully sets up the test server to respond to a metadatum Get request.
+func HandleMetadatumGetSuccessfully(t *testing.T) {
+ th.Mux.HandleFunc("/servers/1234asdf/metadata/foo", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+ th.TestHeader(t, r, "Accept", "application/json")
+
+ w.WriteHeader(http.StatusOK)
+ w.Header().Add("Content-Type", "application/json")
+ w.Write([]byte(`{ "meta": {"foo":"bar"}}`))
+ })
+}
+
+// HandleMetadatumCreateSuccessfully sets up the test server to respond to a metadatum Create request.
+func HandleMetadatumCreateSuccessfully(t *testing.T) {
+ th.Mux.HandleFunc("/servers/1234asdf/metadata/foo", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "PUT")
+ th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+ th.TestJSONRequest(t, r, `{
+ "meta": {
+ "foo": "bar"
+ }
+ }`)
+
+ w.WriteHeader(http.StatusOK)
+ w.Header().Add("Content-Type", "application/json")
+ w.Write([]byte(`{ "meta": {"foo":"bar"}}`))
+ })
+}
+
+// HandleMetadatumDeleteSuccessfully sets up the test server to respond to a metadatum Delete request.
+func HandleMetadatumDeleteSuccessfully(t *testing.T) {
+ th.Mux.HandleFunc("/servers/1234asdf/metadata/foo", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "DELETE")
+ th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+
+ w.WriteHeader(http.StatusNoContent)
+ })
+}
+
+// HandleMetadataGetSuccessfully sets up the test server to respond to a metadata Get request.
+func HandleMetadataGetSuccessfully(t *testing.T) {
+ th.Mux.HandleFunc("/servers/1234asdf/metadata", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+ th.TestHeader(t, r, "Accept", "application/json")
+
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(`{ "metadata": {"foo":"bar", "this":"that"}}`))
+ })
+}
+
+// HandleMetadataResetSuccessfully sets up the test server to respond to a metadata Create request.
+func HandleMetadataResetSuccessfully(t *testing.T) {
+ th.Mux.HandleFunc("/servers/1234asdf/metadata", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "PUT")
+ th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+ th.TestJSONRequest(t, r, `{
+ "metadata": {
+ "foo": "bar",
+ "this": "that"
+ }
+ }`)
+
+ w.WriteHeader(http.StatusOK)
+ w.Header().Add("Content-Type", "application/json")
+ w.Write([]byte(`{ "metadata": {"foo":"bar", "this":"that"}}`))
+ })
+}
+
+// HandleMetadataUpdateSuccessfully sets up the test server to respond to a metadata Update request.
+func HandleMetadataUpdateSuccessfully(t *testing.T) {
+ th.Mux.HandleFunc("/servers/1234asdf/metadata", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "POST")
+ th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+ th.TestJSONRequest(t, r, `{
+ "metadata": {
+ "foo": "baz",
+ "this": "those"
+ }
+ }`)
+
+ w.WriteHeader(http.StatusOK)
+ w.Header().Add("Content-Type", "application/json")
+ w.Write([]byte(`{ "metadata": {"foo":"baz", "this":"those"}}`))
+ })
+}
+
+// ListAddressesExpected represents an expected repsonse from a ListAddresses request.
+var ListAddressesExpected = map[string][]servers.Address{
+ "public": []servers.Address{
+ {
+ Version: 4,
+ Address: "80.56.136.39",
+ },
+ {
+ Version: 6,
+ Address: "2001:4800:790e:510:be76:4eff:fe04:82a8",
+ },
+ },
+ "private": []servers.Address{
+ {
+ Version: 4,
+ Address: "10.880.3.154",
+ },
+ },
+}
+
+// HandleAddressListSuccessfully sets up the test server to respond to a ListAddresses request.
+func HandleAddressListSuccessfully(t *testing.T) {
+ th.Mux.HandleFunc("/servers/asdfasdfasdf/ips", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+
+ w.Header().Add("Content-Type", "application/json")
+ fmt.Fprintf(w, `{
+ "addresses": {
+ "public": [
+ {
+ "version": 4,
+ "addr": "50.56.176.35"
+ },
+ {
+ "version": 6,
+ "addr": "2001:4800:780e:510:be76:4eff:fe04:84a8"
+ }
+ ],
+ "private": [
+ {
+ "version": 4,
+ "addr": "10.180.3.155"
+ }
+ ]
+ }
+ }`)
+ })
+}
+
+// ListNetworkAddressesExpected represents an expected repsonse from a ListAddressesByNetwork request.
+var ListNetworkAddressesExpected = []servers.Address{
+ {
+ Version: 4,
+ Address: "50.56.176.35",
+ },
+ {
+ Version: 6,
+ Address: "2001:4800:780e:510:be76:4eff:fe04:84a8",
+ },
+}
+
+// HandleNetworkAddressListSuccessfully sets up the test server to respond to a ListAddressesByNetwork request.
+func HandleNetworkAddressListSuccessfully(t *testing.T) {
+ th.Mux.HandleFunc("/servers/asdfasdfasdf/ips/public", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+
+ w.Header().Add("Content-Type", "application/json")
+ fmt.Fprintf(w, `{
+ "public": [
+ {
+ "version": 4,
+ "addr": "50.56.176.35"
+ },
+ {
+ "version": 6,
+ "addr": "2001:4800:780e:510:be76:4eff:fe04:84a8"
+ }
+ ]
+ }`)
+ })
+}
+
+// HandleCreateServerImageSuccessfully sets up the test server to respond to a TestCreateServerImage request.
+func HandleCreateServerImageSuccessfully(t *testing.T) {
+ th.Mux.HandleFunc("/servers/serverimage/action", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "POST")
+ th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+ w.Header().Add("Location", "https://0.0.0.0/images/xxxx-xxxxx-xxxxx-xxxx")
+ w.WriteHeader(http.StatusAccepted)
+ })
+}
+
+// HandlePasswordGetSuccessfully sets up the test server to respond to a password Get request.
+func HandlePasswordGetSuccessfully(t *testing.T) {
+ th.Mux.HandleFunc("/servers/1234asdf/os-server-password", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+ th.TestHeader(t, r, "Accept", "application/json")
+
+ fmt.Fprintf(w, ServerPasswordBody)
+ })
+}
diff --git a/openstack/compute/v2/servers/testing/requests_test.go b/openstack/compute/v2/servers/testing/requests_test.go
new file mode 100644
index 0000000..7db6b93
--- /dev/null
+++ b/openstack/compute/v2/servers/testing/requests_test.go
@@ -0,0 +1,412 @@
+package testing
+
+import (
+ "encoding/base64"
+ "encoding/json"
+ "net/http"
+ "testing"
+
+ "github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
+ "github.com/gophercloud/gophercloud/pagination"
+ th "github.com/gophercloud/gophercloud/testhelper"
+ "github.com/gophercloud/gophercloud/testhelper/client"
+)
+
+func TestListServers(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ HandleServerListSuccessfully(t)
+
+ pages := 0
+ err := servers.List(client.ServiceClient(), servers.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
+ pages++
+
+ actual, err := servers.ExtractServers(page)
+ if err != nil {
+ return false, err
+ }
+
+ if len(actual) != 2 {
+ t.Fatalf("Expected 2 servers, got %d", len(actual))
+ }
+ th.CheckDeepEquals(t, ServerHerp, actual[0])
+ th.CheckDeepEquals(t, ServerDerp, actual[1])
+
+ return true, nil
+ })
+
+ th.AssertNoErr(t, err)
+
+ if pages != 1 {
+ t.Errorf("Expected 1 page, saw %d", pages)
+ }
+}
+
+func TestListAllServers(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ HandleServerListSuccessfully(t)
+
+ allPages, err := servers.List(client.ServiceClient(), servers.ListOpts{}).AllPages()
+ th.AssertNoErr(t, err)
+ actual, err := servers.ExtractServers(allPages)
+ th.AssertNoErr(t, err)
+ th.CheckDeepEquals(t, ServerHerp, actual[0])
+ th.CheckDeepEquals(t, ServerDerp, actual[1])
+}
+
+func TestCreateServer(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ HandleServerCreationSuccessfully(t, SingleServerBody)
+
+ actual, err := servers.Create(client.ServiceClient(), servers.CreateOpts{
+ Name: "derp",
+ ImageRef: "f90f6034-2570-4974-8351-6b49732ef2eb",
+ FlavorRef: "1",
+ }).Extract()
+ th.AssertNoErr(t, err)
+
+ th.CheckDeepEquals(t, ServerDerp, *actual)
+}
+
+func TestCreateServerWithCustomField(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ HandleServerCreationWithCustomFieldSuccessfully(t, SingleServerBody)
+
+ actual, err := servers.Create(client.ServiceClient(), CreateOptsWithCustomField{
+ CreateOpts: servers.CreateOpts{
+ Name: "derp",
+ ImageRef: "f90f6034-2570-4974-8351-6b49732ef2eb",
+ FlavorRef: "1",
+ },
+ Foo: "bar",
+ }).Extract()
+ th.AssertNoErr(t, err)
+
+ th.CheckDeepEquals(t, ServerDerp, *actual)
+}
+
+func TestDeleteServer(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ HandleServerDeletionSuccessfully(t)
+
+ res := servers.Delete(client.ServiceClient(), "asdfasdfasdf")
+ th.AssertNoErr(t, res.Err)
+}
+
+func TestForceDeleteServer(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ HandleServerForceDeletionSuccessfully(t)
+
+ res := servers.ForceDelete(client.ServiceClient(), "asdfasdfasdf")
+ th.AssertNoErr(t, res.Err)
+}
+
+func TestGetServer(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ HandleServerGetSuccessfully(t)
+
+ client := client.ServiceClient()
+ actual, err := servers.Get(client, "1234asdf").Extract()
+ if err != nil {
+ t.Fatalf("Unexpected Get error: %v", err)
+ }
+
+ th.CheckDeepEquals(t, ServerDerp, *actual)
+}
+
+func TestUpdateServer(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ HandleServerUpdateSuccessfully(t)
+
+ client := client.ServiceClient()
+ actual, err := servers.Update(client, "1234asdf", servers.UpdateOpts{Name: "new-name"}).Extract()
+ if err != nil {
+ t.Fatalf("Unexpected Update error: %v", err)
+ }
+
+ th.CheckDeepEquals(t, ServerDerp, *actual)
+}
+
+func TestChangeServerAdminPassword(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ HandleAdminPasswordChangeSuccessfully(t)
+
+ res := servers.ChangeAdminPassword(client.ServiceClient(), "1234asdf", "new-password")
+ th.AssertNoErr(t, res.Err)
+}
+
+func TestGetPassword(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ HandlePasswordGetSuccessfully(t)
+
+ res := servers.GetPassword(client.ServiceClient(), "1234asdf")
+ th.AssertNoErr(t, res.Err)
+}
+
+func TestRebootServer(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ HandleRebootSuccessfully(t)
+
+ res := servers.Reboot(client.ServiceClient(), "1234asdf", &servers.RebootOpts{
+ Type: servers.SoftReboot,
+ })
+ th.AssertNoErr(t, res.Err)
+}
+
+func TestRebuildServer(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ HandleRebuildSuccessfully(t, SingleServerBody)
+
+ opts := servers.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 := servers.Rebuild(client.ServiceClient(), "1234asdf", opts).Extract()
+ th.AssertNoErr(t, err)
+
+ th.CheckDeepEquals(t, ServerDerp, *actual)
+}
+
+func TestResizeServer(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "POST")
+ th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+ th.TestJSONRequest(t, r, `{ "resize": { "flavorRef": "2" } }`)
+
+ w.WriteHeader(http.StatusAccepted)
+ })
+
+ res := servers.Resize(client.ServiceClient(), "1234asdf", servers.ResizeOpts{FlavorRef: "2"})
+ th.AssertNoErr(t, res.Err)
+}
+
+func TestConfirmResize(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "POST")
+ th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+ th.TestJSONRequest(t, r, `{ "confirmResize": null }`)
+
+ w.WriteHeader(http.StatusNoContent)
+ })
+
+ res := servers.ConfirmResize(client.ServiceClient(), "1234asdf")
+ th.AssertNoErr(t, res.Err)
+}
+
+func TestRevertResize(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "POST")
+ th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+ th.TestJSONRequest(t, r, `{ "revertResize": null }`)
+
+ w.WriteHeader(http.StatusAccepted)
+ })
+
+ res := servers.RevertResize(client.ServiceClient(), "1234asdf")
+ th.AssertNoErr(t, res.Err)
+}
+
+func TestRescue(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ HandleServerRescueSuccessfully(t)
+
+ res := servers.Rescue(client.ServiceClient(), "1234asdf", servers.RescueOpts{
+ AdminPass: "1234567890",
+ })
+ th.AssertNoErr(t, res.Err)
+ adminPass, _ := res.Extract()
+ th.AssertEquals(t, "1234567890", adminPass)
+}
+
+func TestGetMetadatum(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ HandleMetadatumGetSuccessfully(t)
+
+ expected := map[string]string{"foo": "bar"}
+ actual, err := servers.Metadatum(client.ServiceClient(), "1234asdf", "foo").Extract()
+ th.AssertNoErr(t, err)
+ th.AssertDeepEquals(t, expected, actual)
+}
+
+func TestCreateMetadatum(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ HandleMetadatumCreateSuccessfully(t)
+
+ expected := map[string]string{"foo": "bar"}
+ actual, err := servers.CreateMetadatum(client.ServiceClient(), "1234asdf", servers.MetadatumOpts{"foo": "bar"}).Extract()
+ th.AssertNoErr(t, err)
+ th.AssertDeepEquals(t, expected, actual)
+}
+
+func TestDeleteMetadatum(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ HandleMetadatumDeleteSuccessfully(t)
+
+ err := servers.DeleteMetadatum(client.ServiceClient(), "1234asdf", "foo").ExtractErr()
+ th.AssertNoErr(t, err)
+}
+
+func TestGetMetadata(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ HandleMetadataGetSuccessfully(t)
+
+ expected := map[string]string{"foo": "bar", "this": "that"}
+ actual, err := servers.Metadata(client.ServiceClient(), "1234asdf").Extract()
+ th.AssertNoErr(t, err)
+ th.AssertDeepEquals(t, expected, actual)
+}
+
+func TestResetMetadata(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ HandleMetadataResetSuccessfully(t)
+
+ expected := map[string]string{"foo": "bar", "this": "that"}
+ actual, err := servers.ResetMetadata(client.ServiceClient(), "1234asdf", servers.MetadataOpts{
+ "foo": "bar",
+ "this": "that",
+ }).Extract()
+ th.AssertNoErr(t, err)
+ th.AssertDeepEquals(t, expected, actual)
+}
+
+func TestUpdateMetadata(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ HandleMetadataUpdateSuccessfully(t)
+
+ expected := map[string]string{"foo": "baz", "this": "those"}
+ actual, err := servers.UpdateMetadata(client.ServiceClient(), "1234asdf", servers.MetadataOpts{
+ "foo": "baz",
+ "this": "those",
+ }).Extract()
+ th.AssertNoErr(t, err)
+ th.AssertDeepEquals(t, expected, actual)
+}
+
+func TestListAddresses(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ HandleAddressListSuccessfully(t)
+
+ expected := ListAddressesExpected
+ pages := 0
+ err := servers.ListAddresses(client.ServiceClient(), "asdfasdfasdf").EachPage(func(page pagination.Page) (bool, error) {
+ pages++
+
+ actual, err := servers.ExtractAddresses(page)
+ th.AssertNoErr(t, err)
+
+ if len(actual) != 2 {
+ t.Fatalf("Expected 2 networks, got %d", len(actual))
+ }
+ th.CheckDeepEquals(t, expected, actual)
+
+ return true, nil
+ })
+ th.AssertNoErr(t, err)
+ th.CheckEquals(t, 1, pages)
+}
+
+func TestListAddressesByNetwork(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ HandleNetworkAddressListSuccessfully(t)
+
+ expected := ListNetworkAddressesExpected
+ pages := 0
+ err := servers.ListAddressesByNetwork(client.ServiceClient(), "asdfasdfasdf", "public").EachPage(func(page pagination.Page) (bool, error) {
+ pages++
+
+ actual, err := servers.ExtractNetworkAddresses(page)
+ th.AssertNoErr(t, err)
+
+ if len(actual) != 2 {
+ t.Fatalf("Expected 2 addresses, got %d", len(actual))
+ }
+ th.CheckDeepEquals(t, expected, actual)
+
+ return true, nil
+ })
+ th.AssertNoErr(t, err)
+ th.CheckEquals(t, 1, pages)
+}
+
+func TestCreateServerImage(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ HandleCreateServerImageSuccessfully(t)
+
+ _, err := servers.CreateImage(client.ServiceClient(), "serverimage", servers.CreateImageOpts{Name: "test"}).ExtractImageID()
+ th.AssertNoErr(t, err)
+}
+
+func TestMarshalPersonality(t *testing.T) {
+ name := "/etc/test"
+ contents := []byte("asdfasdf")
+
+ personality := servers.Personality{
+ &servers.File{
+ Path: name,
+ Contents: contents,
+ },
+ }
+
+ data, err := json.Marshal(personality)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var actual []map[string]string
+ err = json.Unmarshal(data, &actual)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(actual) != 1 {
+ t.Fatal("expected personality length 1")
+ }
+
+ if actual[0]["path"] != name {
+ t.Fatal("file path incorrect")
+ }
+
+ if actual[0]["contents"] != base64.StdEncoding.EncodeToString(contents) {
+ t.Fatal("file contents incorrect")
+ }
+}
diff --git a/openstack/compute/v2/servers/testing/results_test.go b/openstack/compute/v2/servers/testing/results_test.go
new file mode 100644
index 0000000..e5334f0
--- /dev/null
+++ b/openstack/compute/v2/servers/testing/results_test.go
@@ -0,0 +1,98 @@
+package testing
+
+import (
+ "crypto/rsa"
+ "encoding/json"
+ "fmt"
+ "testing"
+
+ "github.com/gophercloud/gophercloud"
+ "github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
+ th "github.com/gophercloud/gophercloud/testhelper"
+ "golang.org/x/crypto/ssh"
+)
+
+// Fail - No password in JSON.
+func TestExtractPassword_no_pwd_data(t *testing.T) {
+
+ var dejson interface{}
+ err := json.Unmarshal([]byte(`{ "Crappy data": ".-.-." }`), &dejson)
+ if err != nil {
+ t.Fatalf("%s", err)
+ }
+ resp := servers.GetPasswordResult{gophercloud.Result{Body: dejson}}
+
+ pwd, err := resp.ExtractPassword(nil)
+ th.AssertEquals(t, pwd, "")
+}
+
+// Ok - return encrypted password when no private key is given.
+func TestExtractPassword_encrypted_pwd(t *testing.T) {
+
+ var dejson interface{}
+ sejson := []byte(`{"password":"PP8EnwPO9DhEc8+O/6CKAkPF379mKsUsfFY6yyw0734XXvKsSdV9KbiHQ2hrBvzeZxtGMrlFaikVunCRizyLLWLMuOi4hoH+qy9F9sQid61gQIGkxwDAt85d/7Eau2/KzorFnZhgxArl7IiqJ67X6xjKkR3zur+Yp3V/mtVIehpPYIaAvPbcp2t4mQXl1I9J8yrQfEZOctLL1L4heDEVXnxvNihVLK6pivlVggp6SZCtjj9cduZGrYGsxsOCso1dqJQr7GCojfwvuLOoG0OYwEGuWVTZppxWxi/q1QgeHFhGKA5QUXlz7pS71oqpjYsTeViuHnfvlqb5TVYZpQ1haw=="}`)
+
+ err := json.Unmarshal(sejson, &dejson)
+ fmt.Printf("%v\n", dejson)
+ if err != nil {
+ t.Fatalf("%s", err)
+ }
+ resp := servers.GetPasswordResult{gophercloud.Result{Body: dejson}}
+
+ pwd, err := resp.ExtractPassword(nil)
+ th.AssertNoErr(t, err)
+ th.AssertEquals(t, "PP8EnwPO9DhEc8+O/6CKAkPF379mKsUsfFY6yyw0734XXvKsSdV9KbiHQ2hrBvzeZxtGMrlFaikVunCRizyLLWLMuOi4hoH+qy9F9sQid61gQIGkxwDAt85d/7Eau2/KzorFnZhgxArl7IiqJ67X6xjKkR3zur+Yp3V/mtVIehpPYIaAvPbcp2t4mQXl1I9J8yrQfEZOctLL1L4heDEVXnxvNihVLK6pivlVggp6SZCtjj9cduZGrYGsxsOCso1dqJQr7GCojfwvuLOoG0OYwEGuWVTZppxWxi/q1QgeHFhGKA5QUXlz7pS71oqpjYsTeViuHnfvlqb5TVYZpQ1haw==", pwd)
+}
+
+// Ok - return decrypted password when private key is given.
+// Decrytion can be verified by:
+// echo "<enc_pwd>" | base64 -D | openssl rsautl -decrypt -inkey <privateKey.pem>
+func TestExtractPassword_decrypted_pwd(t *testing.T) {
+
+ privateKey, err := ssh.ParseRawPrivateKey([]byte(`
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEAo1ODZgwMVdTJYim9UYuYhowoPMhGEuV5IRZjcJ315r7RBSC+
+yEiBb1V+jhf+P8fzAyU35lkBzZGDr7E3jxSesbOuYT8cItQS4ErUnI1LGuqvMxwv
+X3GMyE/HmOcaiODF1XZN3Ur5pMJdVknnmczgUsW0hT98Udrh3MQn9WSuh/6LRy6+
+x1QsKHOCLFPnkhWa3LKyxmpQq/Gvhz+6NLe+gt8MFullA5mKQxBJ/K6laVHeaMlw
+JG3GCX0EZhRlvzoV8koIBKZtbKFolFr8ZtxBm3R5LvnyrtOvp22sa+xeItUT5kG1
+ZnbGNdK87oYW+VigEUfzT/+8R1i6E2QIXoeZiQIDAQABAoIBAQCVZ70IqbbTAW8j
+RAlyQh/J3Qal65LmkFJJKUDX8TfT1/Q/G6BKeMEmxm+Zrmsfj1pHI1HKftt+YEG1
+g4jOc09kQXkgbmnfll6aHPn3J+1vdwXD3GGdjrL5PrnYrngAhJWU2r8J0x8hT8ew
+OrUJZXhDX6XuSpAAFRmOKUZgXbSmo4X+LZX76ACnarselJt5FL724ECvpWJ7xxC4
+FMzvp4RqMmNFvv/Uq9lE/EmoSk4dviYyIZZ16DbDNyc9k/sGqCAMktCEwZ3EQm//
+S5bkNhgP6oUXjluWy53aPRgykEylgDWo5SSdSEyKnw/fciU0xdprA9JrBGIcTyHS
+/k2kgD4xAoGBANTkJ88Q0YrxX3fZNZVqcn00XKTxPGmxN5LRs7eV743q30AxK5Db
+QU8iwaAA1IKUWV5DLhgUTNsDCOPUPue4aOSBD3/sj+WEmvIhj7afDL5didkYHsqf
+fDnhFHq7y/3i57d428C7BwwR79pGWVyi7vH3pfu9A1iwl1aNOae+zvbVAoGBAMRm
+AmwQ9fJ3Qc44jysFK/yliLRGdShjkMMah5G3JlrelwfPtwPwEL2EHHhJB/C1acMs
+n6Q6RaoF6WNSZUY65ksQg7aPOYf2X0FTFwQJvwDJ4qlWjmq7w+tQ0AoGJG+dVUmQ
+zHZ/Y+HokSXzz9c4oevk4v/rMgAQ00WHrTdtIhnlAoGBALIJJ72D7CkNGHCq5qPQ
+xHQukPejgolFGhufYXM7YX3GmPMe67cVlTVv9Isxhoa5N0+cUPT0LR3PGOUm/4Bb
+eOT3hZXOqLwhvE6XgI8Rzd95bClwgXekDoh80dqeKMdmta961BQGlKskaPiacmsF
+G1yhZV70P9Mwwy8vpbLB4GUNAoGAbTwbjsWkNfa0qCF3J8NZoszjCvnBQfSW2J1R
+1+8ZKyNwt0yFi3Ajr3TibNiZzPzp1T9lj29FvfpJxA9Y+sXZvthxmcFxizix5GB1
+ha5yCNtA8VSOI7lJkAFDpL+j1lyYyjD6N9JE2KqEyKoh6J+8F7sXsqW7CqRRDfQX
+mKNfey0CgYEAxcEoNoADN2hRl7qY9rbQfVvQb3RkoQkdHhl9gpLFCcV32IP8R4xg
+09NbQK5OmgcIuZhLVNzTmUHJbabEGeXqIFIV0DsqECAt3WzbDyKQO23VJysFD46c
+KSde3I0ybDz7iS2EtceKB7m4C0slYd+oBkm4efuF00rCOKDwpFq45m0=
+-----END RSA PRIVATE KEY-----
+`))
+ if err != nil {
+ t.Fatalf("Error parsing private key: %s\n", err)
+ }
+
+ var dejson interface{}
+ sejson := []byte(`{"password":"PP8EnwPO9DhEc8+O/6CKAkPF379mKsUsfFY6yyw0734XXvKsSdV9KbiHQ2hrBvzeZxtGMrlFaikVunCRizyLLWLMuOi4hoH+qy9F9sQid61gQIGkxwDAt85d/7Eau2/KzorFnZhgxArl7IiqJ67X6xjKkR3zur+Yp3V/mtVIehpPYIaAvPbcp2t4mQXl1I9J8yrQfEZOctLL1L4heDEVXnxvNihVLK6pivlVggp6SZCtjj9cduZGrYGsxsOCso1dqJQr7GCojfwvuLOoG0OYwEGuWVTZppxWxi/q1QgeHFhGKA5QUXlz7pS71oqpjYsTeViuHnfvlqb5TVYZpQ1haw=="}`)
+
+ err = json.Unmarshal(sejson, &dejson)
+ fmt.Printf("%v\n", dejson)
+ if err != nil {
+ t.Fatalf("%s", err)
+ }
+ resp := servers.GetPasswordResult{gophercloud.Result{Body: dejson}}
+
+ pwd, err := resp.ExtractPassword(privateKey.(*rsa.PrivateKey))
+ th.AssertNoErr(t, err)
+ th.AssertEquals(t, "ruZKK0tqxRfYm5t7lSJq", pwd)
+}