Skip to content
This repository was archived by the owner on Aug 1, 2023. It is now read-only.

Commit caaa536

Browse files
authored
Merge pull request #587 from rtrox/admin_actions
[RFR] add admin actions
2 parents 8ec9886 + 6dfdbf5 commit caaa536

File tree

4 files changed

+362
-0
lines changed

4 files changed

+362
-0
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/*
2+
Package provides access to the "Admin Actions" of the Compute API,
3+
including migrations, live-migrations, reset-state, etc.
4+
*/
5+
package adminactions
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// +build fixtures
2+
3+
package adminactions
4+
5+
import (
6+
"net/http"
7+
"testing"
8+
9+
th "github.com/rackspace/gophercloud/testhelper"
10+
"github.com/rackspace/gophercloud/testhelper/client"
11+
)
12+
13+
func mockCreateBackupResponse(t *testing.T, id string) {
14+
th.Mux.HandleFunc("/servers/"+id+"/action", func(w http.ResponseWriter, r *http.Request) {
15+
th.TestMethod(t, r, "POST")
16+
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
17+
th.TestJSONRequest(t, r, `{"createBackup": {"name": "Backup 1", "backup_type": "daily", "rotation": 1}}`)
18+
w.WriteHeader(http.StatusAccepted)
19+
})
20+
}
21+
22+
func mockInjectNetworkInfoResponse(t *testing.T, id string) {
23+
th.Mux.HandleFunc("/servers/"+id+"/action", func(w http.ResponseWriter, r *http.Request) {
24+
th.TestMethod(t, r, "POST")
25+
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
26+
th.TestJSONRequest(t, r, `{"injectNetworkInfo": ""}`)
27+
w.WriteHeader(http.StatusAccepted)
28+
})
29+
}
30+
31+
func mockMigrateResponse(t *testing.T, id string) {
32+
th.Mux.HandleFunc("/servers/"+id+"/action", func(w http.ResponseWriter, r *http.Request) {
33+
th.TestMethod(t, r, "POST")
34+
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
35+
th.TestJSONRequest(t, r, `{"migrate": ""}`)
36+
w.WriteHeader(http.StatusAccepted)
37+
})
38+
}
39+
40+
const liveMigrateRequest = `{"os-migrateLive": {"host": "", "disk_over_commit": false, "block_migration": true}}`
41+
const targetLiveMigrateRequest = `{"os-migrateLive": {"host": "target-compute", "disk_over_commit": false, "block_migration": true}}`
42+
43+
func mockLiveMigrateResponse(t *testing.T, id string) {
44+
th.Mux.HandleFunc("/servers/"+id+"/action", func(w http.ResponseWriter, r *http.Request) {
45+
th.TestMethod(t, r, "POST")
46+
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
47+
th.TestJSONRequest(t, r, liveMigrateRequest)
48+
w.WriteHeader(http.StatusAccepted)
49+
})
50+
}
51+
52+
func mockTargetLiveMigrateResponse(t *testing.T, id string) {
53+
th.Mux.HandleFunc("/servers/"+id+"/action", func(w http.ResponseWriter, r *http.Request) {
54+
th.TestMethod(t, r, "POST")
55+
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
56+
th.TestJSONRequest(t, r, targetLiveMigrateRequest)
57+
w.WriteHeader(http.StatusAccepted)
58+
})
59+
}
60+
61+
func mockResetNetworkResponse(t *testing.T, id string) {
62+
th.Mux.HandleFunc("/servers/"+id+"/action", func(w http.ResponseWriter, r *http.Request) {
63+
th.TestMethod(t, r, "POST")
64+
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
65+
th.TestJSONRequest(t, r, `{"resetNetwork": ""}`)
66+
w.WriteHeader(http.StatusAccepted)
67+
})
68+
}
69+
70+
func mockResetStateResponse(t *testing.T, id string) {
71+
th.Mux.HandleFunc("/servers/"+id+"/action", func(w http.ResponseWriter, r *http.Request) {
72+
th.TestMethod(t, r, "POST")
73+
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
74+
th.TestJSONRequest(t, r, `{"os-resetState": {"state": "active"}}`)
75+
w.WriteHeader(http.StatusAccepted)
76+
})
77+
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package adminactions
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/rackspace/gophercloud"
7+
)
8+
9+
func actionURL(client *gophercloud.ServiceClient, id string) string {
10+
return client.ServiceURL("servers", id, "action")
11+
}
12+
13+
type CreateBackupOpts struct {
14+
// Name: required, name of the backup.
15+
Name string
16+
17+
// BackupType: required, type of the backup, such as "daily".
18+
BackupType string
19+
20+
// Rotation: the number of backups to retain.
21+
Rotation int
22+
}
23+
24+
// ToBackupCreateMap assembles a request body based on the contents of a CreateOpts.
25+
func (opts CreateBackupOpts) ToCreateBackupMap() (map[string]interface{}, error) {
26+
backup := make(map[string]interface{})
27+
28+
if opts.Name == "" {
29+
return nil, fmt.Errorf("CreateBackupOpts.Name cannot be blank.")
30+
}
31+
if opts.BackupType == "" {
32+
return nil, fmt.Errorf("CreateBackupOpts.BackupType cannot be blank.")
33+
}
34+
if opts.Rotation < 0 {
35+
return nil, fmt.Errorf("CreateBackupOpts.Rotation must 0 or greater.")
36+
}
37+
backup["name"] = opts.Name
38+
backup["backup_type"] = opts.BackupType
39+
backup["rotation"] = opts.Rotation
40+
41+
return map[string]interface{}{"createBackup": backup}, nil
42+
}
43+
44+
// ResetNetwork is the admin operation to create a backup of a Compute Server.
45+
func CreateBackup(client *gophercloud.ServiceClient, id string, opts CreateBackupOpts) gophercloud.ErrResult {
46+
var res gophercloud.ErrResult
47+
48+
req, err := opts.ToCreateBackupMap()
49+
if err != nil {
50+
res.Err = err
51+
return res
52+
}
53+
_, res.Err = client.Post(actionURL(client, id), req, nil, nil)
54+
return res
55+
56+
}
57+
58+
// InjectNetworkInfo is the admin operation which injects network info into a Compute Server.
59+
func InjectNetworkInfo(client *gophercloud.ServiceClient, id string) gophercloud.ErrResult {
60+
var req struct {
61+
InjectNetworkInfo string `json:"injectNetworkInfo"`
62+
}
63+
64+
var res gophercloud.ErrResult
65+
_, res.Err = client.Post(actionURL(client, id), req, nil, nil)
66+
return res
67+
}
68+
69+
// Migrate is the admin operation to migrate a Compute Server.
70+
func Migrate(client *gophercloud.ServiceClient, id string) gophercloud.ErrResult {
71+
var req struct {
72+
Migrate string `json:"migrate"`
73+
}
74+
75+
var res gophercloud.ErrResult
76+
_, res.Err = client.Post(actionURL(client, id), req, nil, nil)
77+
return res
78+
}
79+
80+
type LiveMigrateOpts struct {
81+
// Host: optional, If you omit this parameter, the scheduler chooses a host.
82+
Host string
83+
84+
// BlockMigration: defaults to false. Set to true to migrate local disks
85+
// by using block migration. If the source or destination host uses shared storage
86+
// and you set this value to true, the live migration fails.
87+
BlockMigration bool
88+
89+
//DiskOverCommit: defaults to false. Set to true to enable over commit when the
90+
// destination host is checked for available disk space.
91+
DiskOverCommit bool
92+
}
93+
94+
// ToServerCreateMap assembles a request body based on the contents of a CreateOpts.
95+
func (opts LiveMigrateOpts) ToLiveMigrateMap() (map[string]interface{}, error) {
96+
migration := make(map[string]interface{})
97+
98+
migration["host"] = opts.Host
99+
migration["block_migration"] = opts.BlockMigration
100+
migration["disk_over_commit"] = opts.DiskOverCommit
101+
102+
return map[string]interface{}{"os-migrateLive": migration}, nil
103+
}
104+
105+
// ResetNetwork is the admin operation to reset the network on a Compute Server.
106+
func LiveMigrate(client *gophercloud.ServiceClient, id string, opts LiveMigrateOpts) gophercloud.ErrResult {
107+
var res gophercloud.ErrResult
108+
109+
req, err := opts.ToLiveMigrateMap()
110+
if err != nil {
111+
res.Err = err
112+
return res
113+
}
114+
115+
_, res.Err = client.Post(actionURL(client, id), req, nil, nil)
116+
return res
117+
118+
}
119+
120+
// ResetNetwork is the admin operation to reset the network on a Compute Server.
121+
func ResetNetwork(client *gophercloud.ServiceClient, id string) gophercloud.ErrResult {
122+
var req struct {
123+
ResetNetwork string `json:"resetNetwork"`
124+
}
125+
126+
var res gophercloud.ErrResult
127+
_, res.Err = client.Post(actionURL(client, id), req, nil, nil)
128+
return res
129+
}
130+
131+
// ResetState is the admin operation to reset the state of a server.
132+
func ResetState(client *gophercloud.ServiceClient, id string, state string) gophercloud.ErrResult {
133+
var res gophercloud.ErrResult
134+
var req struct {
135+
ResetState struct {
136+
State string `json:"state"`
137+
} `json:"os-resetState"`
138+
}
139+
req.ResetState.State = state
140+
141+
_, res.Err = client.Post(actionURL(client, id), req, nil, nil)
142+
return res
143+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package adminactions
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
th "github.com/rackspace/gophercloud/testhelper"
8+
"github.com/rackspace/gophercloud/testhelper/client"
9+
)
10+
11+
const serverID = "adef1234"
12+
13+
func TestCreateBackup(t *testing.T) {
14+
th.SetupHTTP()
15+
defer th.TeardownHTTP()
16+
17+
mockCreateBackupResponse(t, serverID)
18+
19+
err := CreateBackup(client.ServiceClient(), serverID, CreateBackupOpts{
20+
Name: "Backup 1",
21+
BackupType: "daily",
22+
Rotation: 1,
23+
}).ExtractErr()
24+
th.AssertNoErr(t, err)
25+
}
26+
27+
func TestCreateBackupNoName(t *testing.T) {
28+
th.SetupHTTP()
29+
defer th.TeardownHTTP()
30+
31+
mockCreateBackupResponse(t, serverID)
32+
33+
err := CreateBackup(client.ServiceClient(), serverID, CreateBackupOpts{
34+
BackupType: "daily",
35+
Rotation: 1,
36+
}).ExtractErr()
37+
if err == nil {
38+
fmt.Errorf("CreateBackup without a specified Name should throw an Error.")
39+
}
40+
}
41+
42+
func TestCreateBackupNegativeRotation(t *testing.T) {
43+
th.SetupHTTP()
44+
defer th.TeardownHTTP()
45+
46+
mockCreateBackupResponse(t, serverID)
47+
48+
err := CreateBackup(client.ServiceClient(), serverID, CreateBackupOpts{
49+
Name: "Backup 1",
50+
BackupType: "daily",
51+
Rotation: -1,
52+
}).ExtractErr()
53+
if err == nil {
54+
fmt.Errorf("CreateBackup without a negative Rotation should throw an Error.")
55+
}
56+
}
57+
58+
func TestCreateBackupNoType(t *testing.T) {
59+
th.SetupHTTP()
60+
defer th.TeardownHTTP()
61+
62+
mockCreateBackupResponse(t, serverID)
63+
64+
err := CreateBackup(client.ServiceClient(), serverID, CreateBackupOpts{
65+
Name: "Backup 1",
66+
67+
Rotation: 1,
68+
}).ExtractErr()
69+
if err == nil {
70+
fmt.Errorf("CreateBackup without a specified BackupType should throw an Error.")
71+
}
72+
}
73+
74+
func TestInjectNetworkInfo(t *testing.T) {
75+
th.SetupHTTP()
76+
defer th.TeardownHTTP()
77+
78+
mockInjectNetworkInfoResponse(t, serverID)
79+
80+
err := InjectNetworkInfo(client.ServiceClient(), serverID).ExtractErr()
81+
th.AssertNoErr(t, err)
82+
}
83+
84+
func TestMigrate(t *testing.T) {
85+
th.SetupHTTP()
86+
defer th.TeardownHTTP()
87+
88+
mockMigrateResponse(t, serverID)
89+
90+
err := Migrate(client.ServiceClient(), serverID).ExtractErr()
91+
th.AssertNoErr(t, err)
92+
}
93+
94+
func TestLiveMigrate(t *testing.T) {
95+
th.SetupHTTP()
96+
defer th.TeardownHTTP()
97+
98+
mockLiveMigrateResponse(t, serverID)
99+
100+
err := LiveMigrate(client.ServiceClient(), serverID, LiveMigrateOpts{
101+
BlockMigration: true,
102+
}).ExtractErr()
103+
th.AssertNoErr(t, err)
104+
}
105+
106+
func TestTargetLiveMigrate(t *testing.T) {
107+
th.SetupHTTP()
108+
defer th.TeardownHTTP()
109+
110+
mockTargetLiveMigrateResponse(t, serverID)
111+
112+
err := LiveMigrate(client.ServiceClient(), serverID, LiveMigrateOpts{
113+
Host: "target-compute",
114+
BlockMigration: true,
115+
}).ExtractErr()
116+
th.AssertNoErr(t, err)
117+
}
118+
119+
func TestResetNetwork(t *testing.T) {
120+
th.SetupHTTP()
121+
defer th.TeardownHTTP()
122+
123+
mockResetNetworkResponse(t, serverID)
124+
125+
err := ResetNetwork(client.ServiceClient(), serverID).ExtractErr()
126+
th.AssertNoErr(t, err)
127+
}
128+
129+
func TestResetState(t *testing.T) {
130+
th.SetupHTTP()
131+
defer th.TeardownHTTP()
132+
133+
mockResetStateResponse(t, serverID)
134+
135+
err := ResetState(client.ServiceClient(), serverID, "active").ExtractErr()
136+
th.AssertNoErr(t, err)
137+
}

0 commit comments

Comments
 (0)