Merge pull request #54 from rackspace/resize-server
Add resize server action.
diff --git a/acceptance/09-resize-server.go b/acceptance/09-resize-server.go
new file mode 100644
index 0000000..558b2cf
--- /dev/null
+++ b/acceptance/09-resize-server.go
@@ -0,0 +1,122 @@
+package main
+
+import (
+ "fmt"
+ "flag"
+ "github.com/rackspace/gophercloud"
+ "time"
+)
+
+var quiet = flag.Bool("quiet", false, "Quiet mode, for acceptance testing. $? still indicates errors though.")
+
+func main() {
+ provider, username, password := getCredentials()
+ flag.Parse()
+
+ acc, err := gophercloud.Authenticate(
+ provider,
+ gophercloud.AuthOptions{
+ Username: username,
+ Password: password,
+ },
+ )
+ if err != nil {
+ panic(err)
+ }
+
+ api, err := gophercloud.ServersApi(acc, gophercloud.ApiCriteria{
+ Name: "cloudServersOpenStack",
+ Region: "DFW",
+ VersionId: "2",
+ UrlChoice: gophercloud.PublicURL,
+ })
+ if err != nil {
+ panic(err)
+ }
+
+ // These tests are going to take some time to complete.
+ // So, we'll do two tests at the same time to help amortize test time.
+ done := make(chan bool)
+ go resizeRejectTest(api, done)
+ go resizeAcceptTest(api, done)
+ _ = <- done
+ _ = <- done
+
+ if !*quiet {
+ fmt.Println("Done.")
+ }
+}
+
+func resizeRejectTest(api gophercloud.CloudServersProvider, done chan bool) {
+ withServer(api, func(id string) {
+ newFlavorId := findAlternativeFlavor()
+ err := api.ResizeServer(id, randomString("ACPTTEST", 24), newFlavorId, "")
+ if err != nil {
+ panic(err)
+ }
+
+ waitForVerifyResize(api, id)
+
+ err = api.RevertResize(id)
+ if err != nil {
+ panic(err)
+ }
+ })
+ done <- true
+}
+
+func resizeAcceptTest(api gophercloud.CloudServersProvider, done chan bool) {
+ withServer(api, func(id string) {
+ newFlavorId := findAlternativeFlavor()
+ err := api.ResizeServer(id, randomString("ACPTTEST", 24), newFlavorId, "")
+ if err != nil {
+ panic(err)
+ }
+
+ waitForVerifyResize(api, id)
+
+ err = api.ConfirmResize(id)
+ if err != nil {
+ panic(err)
+ }
+ })
+ done <- true
+}
+
+func waitForVerifyResize(api gophercloud.CloudServersProvider, id string) {
+ for {
+ s, err := api.ServerById(id)
+ if err != nil {
+ panic(err)
+ }
+ if s.Status == "VERIFY_RESIZE" {
+ break
+ }
+ time.Sleep(10 * time.Second)
+ }
+}
+
+func withServer(api gophercloud.CloudServersProvider, f func(string)) {
+ id, err := createServer(api, "", "", "", "")
+ if err != nil {
+ panic(err)
+ }
+
+ for {
+ s, err := api.ServerById(id)
+ if err != nil {
+ panic(err)
+ }
+ if s.Status == "ACTIVE" {
+ break
+ }
+ time.Sleep(10 * time.Second)
+ }
+
+ f(id)
+
+ err = api.DeleteServerById(id)
+ if err != nil {
+ panic(err)
+ }
+}
diff --git a/acceptance/libargs.go b/acceptance/libargs.go
index 405938a..67858f1 100644
--- a/acceptance/libargs.go
+++ b/acceptance/libargs.go
@@ -122,3 +122,9 @@
return newServer.Id, nil
}
+
+// findAlternativeFlavor locates a flavor to resize a server to. It is guaranteed to be different
+// than what aSuitableFlavor() returns. If none could be found, this function will panic.
+func findAlternativeFlavor() string {
+ return "3" // 1GB image, up from 512MB image
+}
\ No newline at end of file
diff --git a/interfaces.go b/interfaces.go
index 089b6dd..de1f7c8 100644
--- a/interfaces.go
+++ b/interfaces.go
@@ -32,6 +32,9 @@
CreateServer(ns NewServer) (*NewServer, error)
DeleteServerById(id string) error
SetAdminPassword(id string, pw string) error
+ ResizeServer(id, newName, newFlavor, newDiskConfig string) error
+ RevertResize(id string) error
+ ConfirmResize(id string) error
// Images
diff --git a/servers.go b/servers.go
index ee38e9f..78ad350 100644
--- a/servers.go
+++ b/servers.go
@@ -131,6 +131,62 @@
return err
}
+// See the CloudServersProvider interface for details.
+func (gsp *genericServersProvider) ResizeServer(id, newName, newFlavor, newDiskConfig string) error {
+ err := gsp.context.WithReauth(gsp.access, func() error {
+ url := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id)
+ rr := ResizeRequest{
+ Name: newName,
+ FlavorRef: newFlavor,
+ DiskConfig: newDiskConfig,
+ }
+ return perigee.Post(url, perigee.Options{
+ ReqBody: &struct {
+ Resize ResizeRequest `json:"resize"`
+ }{rr},
+ OkCodes: []int{202},
+ MoreHeaders: map[string]string{
+ "X-Auth-Token": gsp.access.AuthToken(),
+ },
+ })
+ })
+ return err
+}
+
+// See the CloudServersProvider interface for details.
+func (gsp *genericServersProvider) RevertResize(id string) error {
+ err := gsp.context.WithReauth(gsp.access, func() error {
+ url := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id)
+ return perigee.Post(url, perigee.Options{
+ ReqBody: &struct {
+ RevertResize *int `json:"revertResize"`
+ }{nil},
+ OkCodes: []int{202},
+ MoreHeaders: map[string]string{
+ "X-Auth-Token": gsp.access.AuthToken(),
+ },
+ })
+ })
+ return err
+}
+
+// See the CloudServersProvider interface for details.
+func (gsp *genericServersProvider) ConfirmResize(id string) error {
+ err := gsp.context.WithReauth(gsp.access, func() error {
+ url := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id)
+ return perigee.Post(url, perigee.Options{
+ ReqBody: &struct {
+ ConfirmResize *int `json:"confirmResize"`
+ }{nil},
+ OkCodes: []int{204},
+ MoreHeaders: map[string]string{
+ "X-Auth-Token": gsp.access.AuthToken(),
+ },
+ })
+ })
+ return err
+}
+
// RaxBandwidth provides measurement of server bandwidth consumed over a given audit interval.
type RaxBandwidth struct {
AuditPeriodEnd string `json:"audit_period_end"`
@@ -313,3 +369,12 @@
Links []Link `json:"links,omitempty"`
OsDcfDiskConfig string `json:"OS-DCF:diskConfig,omitempty"`
}
+
+// ResizeRequest structures are used internally to encode to JSON the parameters required to resize a server instance.
+// Client applications will not use this structure (no API accepts an instance of this structure).
+// See the Region method ResizeServer() for more details on how to resize a server.
+type ResizeRequest struct {
+ Name string `json:"name,omitempty"`
+ FlavorRef string `json:"flavorRef"`
+ DiskConfig string `json:"OS-DCF:diskConfig,omitempty"`
+}