Add remaining server actions, except create image.
Create image action, to leave the user's account in the same state as it was
before, requires we use the Images API to delete the created image. I do not
yet have those tests started, but when I write them, I'll add the create image
test at that time.
diff --git a/acceptance/openstack/compute_test.go b/acceptance/openstack/compute_test.go
index 1cc154a..0a99d05 100644
--- a/acceptance/openstack/compute_test.go
+++ b/acceptance/openstack/compute_test.go
@@ -185,7 +185,9 @@
}
}
-func TestServerAction(t *testing.T) {
+func TestActionChangeAdminPassword(t *testing.T) {
+ t.Parallel()
+
ts, err := setupForCRUD()
if err != nil {
t.Fatal(err)
@@ -209,6 +211,29 @@
if err != nil {
t.Fatal(err)
}
+}
+
+func TestActionReboot(t *testing.T) {
+ t.Parallel()
+
+ ts, err := setupForCRUD()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = createServer(ts)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ defer func(){
+ servers.Delete(ts.client, ts.createdServer.Id)
+ }()
+
+ err = waitForStatus(ts, "ACTIVE")
+ if err != nil {
+ t.Fatal(err)
+ }
err = servers.Reboot(ts.client, ts.createdServer.Id, "aldhjflaskhjf")
if err == nil {
@@ -221,3 +246,96 @@
}
}
+func TestActionRebuild(t *testing.T) {
+ t.Parallel()
+
+ ts, err := setupForCRUD()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = createServer(ts)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ defer func(){
+ servers.Delete(ts.client, ts.createdServer.Id)
+ }()
+
+ err = waitForStatus(ts, "ACTIVE")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = rebuildServer(ts)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestActionResizeConfirm(t *testing.T) {
+ t.Parallel()
+
+ ts, err := setupForCRUD()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = createServer(ts)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ defer func(){
+ servers.Delete(ts.client, ts.createdServer.Id)
+ }()
+
+ err = waitForStatus(ts, "ACTIVE")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = resizeServer(ts)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = confirmResize(ts)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestActionResizeRevert(t *testing.T) {
+ t.Parallel()
+
+ ts, err := setupForCRUD()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = createServer(ts)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ defer func(){
+ servers.Delete(ts.client, ts.createdServer.Id)
+ }()
+
+ err = waitForStatus(ts, "ACTIVE")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = resizeServer(ts)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = revertResize(ts)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
diff --git a/acceptance/openstack/tools_test.go b/acceptance/openstack/tools_test.go
index c447d4a..17377f9 100644
--- a/acceptance/openstack/tools_test.go
+++ b/acceptance/openstack/tools_test.go
@@ -31,6 +31,7 @@
updatedServer *servers.Server
serverName string
alternateName string
+ flavorIdResize string
}
func setupForList() (*testState, error) {
@@ -80,6 +81,15 @@
return ts, fmt.Errorf("Expected OS_FLAVOR_ID environment variable to be set")
}
+ ts.flavorIdResize = os.Getenv("OS_FLAVOR_ID_RESIZE")
+ if ts.flavorIdResize == "" {
+ return ts, fmt.Errorf("Expected OS_FLAVOR_ID_RESIZE environment variable to be set")
+ }
+
+ if ts.flavorIdResize == ts.flavorId {
+ return ts, fmt.Errorf("OS_FLAVOR_ID and OS_FLAVOR_ID_RESIZE cannot be the same")
+ }
+
ts.region = os.Getenv("OS_REGION_NAME")
if ts.region == "" {
ts.region = ts.eps[0].Region
@@ -229,13 +239,18 @@
return err
}
-func changeAdminPassword(ts *testState) error {
- fmt.Println("Current password: "+ts.createdServer.AdminPass)
+func makeNewPassword(oldPass string) string {
+ fmt.Println("Current password: "+oldPass)
randomPassword := randomString("", 16)
- for randomPassword == ts.createdServer.AdminPass {
+ for randomPassword == oldPass {
randomPassword = randomString("", 16)
}
fmt.Println(" New password: "+randomPassword)
+ return randomPassword
+}
+
+func changeAdminPassword(ts *testState) error {
+ randomPassword := makeNewPassword(ts.createdServer.AdminPass)
err := servers.ChangeAdminPassword(ts.client, ts.createdServer.Id, randomPassword)
if err != nil {
@@ -265,6 +280,75 @@
return waitForStatus(ts, "ACTIVE")
}
+func rebuildServer(ts *testState) error {
+ fmt.Println("Attempting to rebuild server "+ts.createdServer.Id)
+
+ newPassword := makeNewPassword(ts.createdServer.AdminPass)
+ newName := randomString("ACPTTEST", 16)
+ sr, err := servers.Rebuild(ts.client, ts.createdServer.Id, newName, newPassword, ts.imageId, nil)
+ if err != nil {
+ return err
+ }
+
+ s, err := servers.GetServer(sr)
+ if err != nil {
+ return err
+ }
+ if s.Id != ts.createdServer.Id {
+ return fmt.Errorf("Expected rebuilt server ID of %s; got %s", ts.createdServer.Id, s.Id)
+ }
+
+ err = waitForStatus(ts, "REBUILD")
+ if err != nil {
+ return err
+ }
+
+ return waitForStatus(ts, "ACTIVE")
+}
+
+func resizeServer(ts *testState) error {
+ fmt.Println("Attempting to resize server "+ts.createdServer.Id)
+
+ err := servers.Resize(ts.client, ts.createdServer.Id, ts.flavorIdResize)
+ if err != nil {
+ return err
+ }
+
+ err = waitForStatus(ts, "RESIZE")
+ if err != nil {
+ return err
+ }
+
+ return waitForStatus(ts, "VERIFY_RESIZE")
+}
+
+func confirmResize(ts *testState) error {
+ fmt.Println("Attempting to confirm resize for server "+ts.createdServer.Id)
+
+ err := servers.ConfirmResize(ts.client, ts.createdServer.Id)
+ if err != nil {
+ return err
+ }
+
+ return waitForStatus(ts, "ACTIVE")
+}
+
+func revertResize(ts *testState) error {
+ fmt.Println("Attempting to revert resize for server "+ts.createdServer.Id)
+
+ err := servers.RevertResize(ts.client, ts.createdServer.Id)
+ if err != nil {
+ return err
+ }
+
+ err = waitForStatus(ts, "REVERT_RESIZE")
+ if err != nil {
+ return err
+ }
+
+ return waitForStatus(ts, "ACTIVE")
+}
+
// randomString generates a string of given length, but random content.
// All content will be within the ASCII graphic character set.
// (Implementation from Even Shaw's contribution on
diff --git a/openstack/compute/images/images.go b/openstack/compute/images/images.go
index cc08589..c881092 100644
--- a/openstack/compute/images/images.go
+++ b/openstack/compute/images/images.go
@@ -44,3 +44,9 @@
}
return images, nil
}
+
+func GetImage(ir ImageResults) (Image, error) {
+ image := Image{}
+ err := mapstructure.Decode(ir, &image)
+ return image, err
+}
diff --git a/openstack/compute/images/images_test.go b/openstack/compute/images/images_test.go
new file mode 100644
index 0000000..05a8550
--- /dev/null
+++ b/openstack/compute/images/images_test.go
@@ -0,0 +1,43 @@
+package images
+
+import (
+ "testing"
+ "encoding/json"
+)
+
+const (
+ // This example was taken from: http://docs.openstack.org/api/openstack-compute/2/content/Rebuild_Server-d1e3538.html
+
+ simpleImageJson = `{
+ "id": "52415800-8b69-11e0-9b19-734f6f006e54",
+ "name": "CentOS 5.2",
+ "links": [{
+ "rel": "self",
+ "href": "http://servers.api.openstack.org/v2/1234/images/52415800-8b69-11e0-9b19-734f6f006e54"
+ },{
+ "rel": "bookmark",
+ "href": "http://servers.api.openstack.org/1234/images/52415800-8b69-11e0-9b19-734f6f006e54"
+ }]
+ }`
+)
+
+func TestGetImage(t *testing.T) {
+ var simpleImageMap map[string]interface{}
+ err := json.Unmarshal([]byte(simpleImageJson), &simpleImageMap)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ image, err := GetImage(simpleImageMap)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if image.Id != "52415800-8b69-11e0-9b19-734f6f006e54" {
+ t.Fatal("I expected an image ID of 52415800-8b69-11e0-9b19-734f6f006e54; got "+image.Id)
+ }
+
+ if image.Name != "CentOS 5.2" {
+ t.Fatal("I expected an image name of CentOS 5.2; got "+image.Name)
+ }
+}
diff --git a/openstack/compute/images/requests.go b/openstack/compute/images/requests.go
index ff875c9..79783c4 100644
--- a/openstack/compute/images/requests.go
+++ b/openstack/compute/images/requests.go
@@ -8,6 +8,7 @@
var ErrNotImplemented = fmt.Errorf("Images functionality not implemented.")
type ListResults map[string]interface{}
+type ImageResults map[string]interface{}
func List(c *Client) (ListResults, error) {
var lr ListResults
diff --git a/openstack/compute/servers/requests.go b/openstack/compute/servers/requests.go
index 207c6de..2a004f6 100644
--- a/openstack/compute/servers/requests.go
+++ b/openstack/compute/servers/requests.go
@@ -122,7 +122,7 @@
return err
}
-// ArgumentError errors occur when an argument supplied to a package function
+// 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
@@ -134,17 +134,17 @@
// Argument identifies which formal argument was responsible for producing the
// error.
// Value provides the value as it was passed into the function.
-type ArgumentError struct {
+type ErrArgument struct {
Function, Argument string
Value interface{}
}
// Error yields a useful diagnostic for debugging purposes.
-func (e *ArgumentError) Error() string {
+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 *ArgumentError) String() string {
+func (e *ErrArgument) String() string {
return e.Error()
}
@@ -171,7 +171,7 @@
// machine.
func Reboot(c *Client, id, how string) error {
if (how != SoftReboot) && (how != HardReboot) {
- return &ArgumentError{
+ return &ErrArgument{
Function: "Reboot",
Argument: "how",
Value: how,
@@ -192,3 +192,128 @@
})
return err
}
+
+// Rebuild requests that the Openstack provider reprovision the
+// server. The rebuild will need to know the server's name and
+// new image reference or ID. In addition, and unlike building
+// a server with Create(), you must provide an administrator
+// password.
+//
+// Additional options may be specified with the additional map.
+// This function treats a nil map the same as an empty map.
+//
+// Rebuild returns a server result as though you had called
+// GetDetail() on the server's ID. The information, however,
+// refers to the new server, not the old.
+func Rebuild(c *Client, id, name, password, imageRef string, additional map[string]interface{}) (ServerResult, error) {
+ var sr ServerResult
+
+ if id == "" {
+ return sr, &ErrArgument{
+ Function: "Rebuild",
+ Argument: "id",
+ Value: "",
+ }
+ }
+
+ if name == "" {
+ return sr, &ErrArgument{
+ Function: "Rebuild",
+ Argument: "name",
+ Value: "",
+ }
+ }
+
+ if password == "" {
+ return sr, &ErrArgument{
+ Function: "Rebuild",
+ Argument: "password",
+ Value: "",
+ }
+ }
+
+ if imageRef == "" {
+ return sr, &ErrArgument{
+ Function: "Rebuild",
+ Argument: "imageRef",
+ Value: "",
+ }
+ }
+
+ if additional == nil {
+ additional = make(map[string]interface{}, 0)
+ }
+
+ additional["name"] = name
+ additional["imageRef"] = imageRef
+ additional["adminPass"] = password
+
+ h, err := c.getActionHeaders()
+ if err != nil {
+ return sr, err
+ }
+
+ err = perigee.Post(c.getActionUrl(id), perigee.Options{
+ ReqBody: struct{R map[string]interface{} `json:"rebuild"`}{
+ additional,
+ },
+ Results: &sr,
+ MoreHeaders: h,
+ OkCodes: []int{202},
+ })
+ return sr, err
+}
+
+// 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(c *Client, id, flavorRef string) error {
+ h, err := c.getActionHeaders()
+ if err != nil {
+ return err
+ }
+
+ err = perigee.Post(c.getActionUrl(id), perigee.Options{
+ ReqBody: struct{R map[string]interface{} `json:"resize"`}{
+ map[string]interface{}{"flavorRef": flavorRef},
+ },
+ MoreHeaders: h,
+ OkCodes: []int{202},
+ })
+ return err
+}
+
+// ConfirmResize confirms a previous resize operation on a server.
+// See Resize() for more details.
+func ConfirmResize(c *Client, id string) error {
+ h, err := c.getActionHeaders()
+ if err != nil {
+ return err
+ }
+
+ err = perigee.Post(c.getActionUrl(id), perigee.Options{
+ ReqBody: map[string]interface{}{"confirmResize": nil},
+ MoreHeaders: h,
+ OkCodes: []int{204},
+ })
+ return err
+}
+
+// RevertResize cancels a previous resize operation on a server.
+// See Resize() for more details.
+func RevertResize(c *Client, id string) error {
+ h, err := c.getActionHeaders()
+ if err != nil {
+ return err
+ }
+
+ err = perigee.Post(c.getActionUrl(id), perigee.Options{
+ ReqBody: map[string]interface{}{"revertResize": nil},
+ MoreHeaders: h,
+ OkCodes: []int{202},
+ })
+ return err
+}