Create, delete, and query server
diff --git a/acceptance/openstack/compute_test.go b/acceptance/openstack/compute_test.go
index 7a478e0..c495ebc 100644
--- a/acceptance/openstack/compute_test.go
+++ b/acceptance/openstack/compute_test.go
@@ -10,6 +10,8 @@
"github.com/rackspace/gophercloud/openstack/utils"
"os"
"text/tabwriter"
+ "time"
+ "crypto/rand"
)
type testState struct {
@@ -20,7 +22,7 @@
w *tabwriter.Writer
}
-func prepForTest() (*testState, error) {
+func setupForList() (*testState, error) {
var err error
ts := new(testState)
@@ -52,13 +54,13 @@
}
func TestListServers(t *testing.T) {
- ts, err := prepForTest()
+ ts, err := setupForList()
if err != nil {
t.Error(err)
return
}
- fmt.Fprintln(ts.w, "ID\tRegion\tName\tIPv4\tIPv6\t")
+ fmt.Fprintln(ts.w, "ID\tRegion\tName\tStatus\tIPv4\tIPv6\t")
region := os.Getenv("OS_REGION_NAME")
n := 0
@@ -84,7 +86,7 @@
n = n + len(svrs)
for _, s := range svrs {
- fmt.Fprintf(ts.w, "%s\t%s\t%s\t%s\t%s\t\n", s.Id, s.Name, ep.Region, s.AccessIPv4, s.AccessIPv6)
+ fmt.Fprintf(ts.w, "%s\t%s\t%s\t%s\t%s\t%s\t\n", s.Id, s.Name, ep.Region, s.Status, s.AccessIPv4, s.AccessIPv6)
}
}
ts.w.Flush()
@@ -92,7 +94,7 @@
}
func TestListImages(t *testing.T) {
- ts, err := prepForTest()
+ ts, err := setupForList()
if err != nil {
t.Error(err)
return
@@ -132,7 +134,7 @@
}
func TestListFlavors(t *testing.T) {
- ts, err := prepForTest()
+ ts, err := setupForList()
if err != nil {
t.Error(err)
return
@@ -186,3 +188,110 @@
return nil, fmt.Errorf("Compute endpoint not found.")
}
+func findEndpointForRegion(eps []identity.Endpoint, r string) (string, error) {
+ for _, ep := range eps {
+ if ep.Region == r {
+ return ep.PublicURL, nil
+ }
+ }
+ return "", fmt.Errorf("Unknown region %s", r)
+}
+
+func TestCreateDestroyServer(t *testing.T) {
+ ts, err := setupForList()
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ imageId := os.Getenv("OS_IMAGE_ID")
+ if imageId == "" {
+ t.Error("Expected OS_IMAGE_ID environment variable to be set")
+ return
+ }
+
+ flavorId := os.Getenv("OS_FLAVOR_ID")
+ if flavorId == "" {
+ t.Error("Expected OS_FLAVOR_ID environment variable to be set")
+ return
+ }
+
+ region := os.Getenv("OS_REGION_NAME")
+ if region == "" {
+ region = ts.eps[0].Region
+ }
+
+ ep, err := findEndpointForRegion(ts.eps, region)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ serverName := randomString("ACPTTEST", 16)
+ fmt.Printf("Attempting to create server: %s\n", serverName)
+
+ client := servers.NewClient(ep, ts.a, ts.o)
+
+ cr, err := servers.Create(client, map[string]interface{}{
+ "flavorRef": flavorId,
+ "imageRef": imageId,
+ "name": serverName,
+ })
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ createdServer, err := servers.GetServer(cr)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ defer func() {
+ servers.Delete(client, createdServer.Id)
+ }()
+
+ timeout := 300
+ for ; timeout > 0; timeout-- {
+ gr, err := servers.GetDetail(client, createdServer.Id)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ gottenServer, err := servers.GetServer(gr)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ if gottenServer.Id != createdServer.Id {
+ t.Error("Created server ID (%s) != gotten server ID (%s)", createdServer.Id, gottenServer.Id)
+ return
+ }
+
+ if gottenServer.Status == "ACTIVE" {
+ fmt.Printf("Server created after %d seconds (approximately)\n", 300-timeout)
+ break
+ }
+ time.Sleep(1*time.Second)
+ }
+ if timeout < 1 {
+ fmt.Printf("I'm not waiting around.\n")
+ }
+}
+
+// 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
+// http://stackoverflow.com/questions/12771930/what-is-the-fastest-way-to-generate-a-long-random-string-in-go).
+func randomString(prefix string, n int) string {
+ const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+ var bytes = make([]byte, n)
+ rand.Read(bytes)
+ for i, b := range bytes {
+ bytes[i] = alphanum[b%byte(len(alphanum))]
+ }
+ return prefix + string(bytes)
+}
+
diff --git a/openstack/compute/servers/client.go b/openstack/compute/servers/client.go
index a59a339..8b7d1fb 100644
--- a/openstack/compute/servers/client.go
+++ b/openstack/compute/servers/client.go
@@ -26,6 +26,18 @@
return fmt.Sprintf("%s/servers/detail", c.endpoint)
}
+func (c *Client) getCreateUrl() string {
+ return fmt.Sprintf("%s/servers", c.endpoint)
+}
+
+func (c *Client) getDeleteUrl(id string) string {
+ return fmt.Sprintf("%s/servers/%s", c.endpoint, id)
+}
+
+func (c *Client) getDetailUrl(id string) string {
+ return c.getDeleteUrl(id)
+}
+
func (c *Client) getListHeaders() (map[string]string, error) {
t, err := c.getAuthToken()
if err != nil {
@@ -37,6 +49,18 @@
}, nil
}
+func (c *Client) getCreateHeaders() (map[string]string, error) {
+ return c.getListHeaders()
+}
+
+func (c *Client) getDeleteHeaders() (map[string]string, error) {
+ return c.getListHeaders()
+}
+
+func (c *Client) getDetailHeaders() (map[string]string, error) {
+ return c.getListHeaders()
+}
+
func (c *Client) getAuthToken() (string, error) {
var err error
diff --git a/openstack/compute/servers/requests.go b/openstack/compute/servers/requests.go
index 0c49159..8506aac 100644
--- a/openstack/compute/servers/requests.go
+++ b/openstack/compute/servers/requests.go
@@ -10,6 +10,8 @@
// provided through separate, type-safe accessors or methods.
type ListResult map[string]interface{}
+type ServerResult map[string]interface{}
+
// List makes a request against the API to list servers accessible to you.
func List(c *Client) (ListResult, error) {
var lr ListResult
@@ -26,3 +28,53 @@
return lr, err
}
+// Create requests a server to be provisioned to the user in the current tenant.
+func Create(c *Client, opts map[string]interface{}) (ServerResult, error) {
+ var sr ServerResult
+
+ h, err := c.getCreateHeaders()
+ if err != nil {
+ return nil, err
+ }
+
+ err = perigee.Post(c.getCreateUrl(), perigee.Options{
+ Results: &sr,
+ ReqBody: map[string]interface{} {
+ "server": opts,
+ },
+ MoreHeaders: h,
+ OkCodes: []int{202},
+ })
+ return sr, err
+}
+
+// Delete requests that a server previously provisioned be removed from your account.
+func Delete(c *Client, id string) error {
+ h, err := c.getDeleteHeaders()
+ if err != nil {
+ return err
+ }
+
+ err = perigee.Delete(c.getDeleteUrl(id), perigee.Options{
+ MoreHeaders: h,
+ OkCodes: []int{204},
+ })
+ return err
+}
+
+// GetDetail requests details on a single server, by ID.
+func GetDetail(c *Client, id string) (ServerResult, error) {
+ var sr ServerResult
+
+ h, err := c.getDetailHeaders()
+ if err != nil {
+ return nil, err
+ }
+
+ err = perigee.Get(c.getDetailUrl(id), perigee.Options{
+ Results: &sr,
+ MoreHeaders: h,
+ })
+ return sr, err
+}
+
diff --git a/openstack/compute/servers/servers.go b/openstack/compute/servers/servers.go
index 7061993..c02cf9f 100644
--- a/openstack/compute/servers/servers.go
+++ b/openstack/compute/servers/servers.go
@@ -75,3 +75,15 @@
return servers, nil
}
+// GetServer interprets the result of a call expected to return data on a single server.
+func GetServer(sr ServerResult) (*Server, error) {
+ so, ok := sr["server"]
+ if !ok {
+ return nil, ErrNotImplemented
+ }
+ serverObj := so.(map[string]interface{})
+ s := new(Server)
+ err := mapstructure.Decode(serverObj, s)
+ return s, err
+}
+