Add resize server action.
This looks like it should do the trick. Intermittently, however, some
servers created by the acceptance test will end up in an error state.
It is not consistently reproducible. When it does work, however, it
works as expected. I'm not sure why the behavior is variable.
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"`
+}