blob: 78fde06f5ff058a9fc196d1e12a9838431f39a2a [file] [log] [blame]
Samuel A. Falvo II704a7502013-07-10 15:23:43 -07001package main
2
3import (
Samuel A. Falvo IIe91ff6d2013-07-11 15:46:10 -07004 "crypto/rand"
Mark Peekd27e2532013-08-27 07:58:09 -07005 "fmt"
Samuel A. Falvo II8935ca32013-07-25 21:27:06 -07006 "github.com/rackspace/gophercloud"
Mark Peekd27e2532013-08-27 07:58:09 -07007 "os"
Samuel A. Falvo II002b6512013-07-29 16:30:40 -07008 "time"
Samuel A. Falvo II704a7502013-07-10 15:23:43 -07009)
10
11// getCredentials will verify existence of needed credential information
12// provided through environment variables. This function will not return
13// if at least one piece of required information is missing.
Rafael Garciae4a550e2013-12-06 17:00:32 -030014func getCredentials() (provider, username, password, apiKey string) {
Samuel A. Falvo II704a7502013-07-10 15:23:43 -070015 provider = os.Getenv("SDK_PROVIDER")
16 username = os.Getenv("SDK_USERNAME")
17 password = os.Getenv("SDK_PASSWORD")
Rafael Garcia752cb332013-12-12 22:16:58 -030018 apiKey = os.Getenv("SDK_API_KEY")
Samuel A. Falvo II704a7502013-07-10 15:23:43 -070019
20 if (provider == "") || (username == "") || (password == "") {
21 fmt.Fprintf(os.Stderr, "One or more of the following environment variables aren't set:\n")
22 fmt.Fprintf(os.Stderr, " SDK_PROVIDER=\"%s\"\n", provider)
23 fmt.Fprintf(os.Stderr, " SDK_USERNAME=\"%s\"\n", username)
24 fmt.Fprintf(os.Stderr, " SDK_PASSWORD=\"%s\"\n", password)
25 os.Exit(1)
26 }
27
28 return
29}
Samuel A. Falvo IIe91ff6d2013-07-11 15:46:10 -070030
31// randomString generates a string of given length, but random content.
32// All content will be within the ASCII graphic character set.
33// (Implementation from Even Shaw's contribution on
34// http://stackoverflow.com/questions/12771930/what-is-the-fastest-way-to-generate-a-long-random-string-in-go).
Samuel A. Falvo IIe3b2d7a2013-07-12 11:08:02 -070035func randomString(prefix string, n int) string {
Mark Peekd27e2532013-08-27 07:58:09 -070036 const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
37 var bytes = make([]byte, n)
38 rand.Read(bytes)
39 for i, b := range bytes {
40 bytes[i] = alphanum[b%byte(len(alphanum))]
41 }
42 return prefix + string(bytes)
Samuel A. Falvo II8935ca32013-07-25 21:27:06 -070043}
44
45// aSuitableImage finds a minimal image for use in dynamically creating servers.
46// If none can be found, this function will panic.
47func aSuitableImage(api gophercloud.CloudServersProvider) string {
48 images, err := api.ListImages()
49 if err != nil {
50 panic(err)
51 }
52
53 // TODO(sfalvo):
54 // Works for Rackspace, might not work for your provider!
55 // Need to figure out why ListImages() provides 0 values for
56 // Ram and Disk fields.
57 //
58 // Until then, just return Ubuntu 12.04 LTS.
59 for i := 0; i < len(images); i++ {
60 if images[i].Id == "23b564c9-c3e6-49f9-bc68-86c7a9ab5018" {
61 return images[i].Id
62 }
63 }
64 panic("Image 23b564c9-c3e6-49f9-bc68-86c7a9ab5018 (Ubuntu 12.04 LTS) not found.")
65}
66
67// aSuitableFlavor finds the minimum flavor capable of running the test image
68// chosen by aSuitableImage. If none can be found, this function will panic.
69func aSuitableFlavor(api gophercloud.CloudServersProvider) string {
70 flavors, err := api.ListFlavors()
71 if err != nil {
72 panic(err)
73 }
74
75 // TODO(sfalvo):
76 // Works for Rackspace, might not work for your provider!
77 // Need to figure out why ListFlavors() provides 0 values for
78 // Ram and Disk fields.
79 //
80 // Until then, just return Ubuntu 12.04 LTS.
81 for i := 0; i < len(flavors); i++ {
82 if flavors[i].Id == "2" {
83 return flavors[i].Id
84 }
85 }
86 panic("Flavor 2 (512MB 1-core 20GB machine) not found.")
87}
88
89// createServer creates a new server in a manner compatible with acceptance testing.
90// In particular, it ensures that the name of the server always starts with "ACPTTEST--",
91// which the delete servers acceptance test relies on to identify servers to delete.
92// Passing in empty image and flavor references will force the use of reasonable defaults.
93// An empty name string will result in a dynamically created name prefixed with "ACPTTEST--".
94// A blank admin password will cause a password to be automatically generated; however,
95// at present no means of recovering this password exists, as no acceptance tests yet require
96// this data.
Samuel A. Falvo II80699602013-07-25 23:35:57 -070097func createServer(servers gophercloud.CloudServersProvider, imageRef, flavorRef, name, adminPass string) (string, error) {
Samuel A. Falvo II8935ca32013-07-25 21:27:06 -070098 if imageRef == "" {
99 imageRef = aSuitableImage(servers)
100 }
101
102 if flavorRef == "" {
103 flavorRef = aSuitableFlavor(servers)
104 }
105
106 if len(name) < 1 {
107 name = randomString("ACPTTEST", 16)
108 }
109
110 if (len(name) < 8) || (name[0:8] != "ACPTTEST") {
111 name = fmt.Sprintf("ACPTTEST--%s", name)
112 }
113
Samuel A. Falvo II80699602013-07-25 23:35:57 -0700114 newServer, err := servers.CreateServer(gophercloud.NewServer{
Samuel A. Falvo II8935ca32013-07-25 21:27:06 -0700115 Name: name,
116 ImageRef: imageRef,
117 FlavorRef: flavorRef,
118 AdminPass: adminPass,
119 })
120
Samuel A. Falvo II80699602013-07-25 23:35:57 -0700121 if err != nil {
122 return "", err
123 }
124
125 return newServer.Id, nil
Samuel A. Falvo II8935ca32013-07-25 21:27:06 -0700126}
Samuel A. Falvo II8512e9a2013-07-26 22:53:29 -0700127
128// findAlternativeFlavor locates a flavor to resize a server to. It is guaranteed to be different
129// than what aSuitableFlavor() returns. If none could be found, this function will panic.
130func findAlternativeFlavor() string {
Mark Peekd27e2532013-08-27 07:58:09 -0700131 return "3" // 1GB image, up from 512MB image
Samuel A. Falvo IIf722dbf2013-07-29 15:44:30 -0700132}
133
Samuel A. Falvo II414c15c2013-08-01 15:16:46 -0700134// findAlternativeImage locates an image to resize or rebuild a server with. It is guaranteed to be
135// different than what aSuitableImage() returns. If none could be found, this function will panic.
136func findAlternativeImage() string {
137 return "c6f9c411-e708-4952-91e5-62ded5ea4d3e"
138}
139
Samuel A. Falvo IIf722dbf2013-07-29 15:44:30 -0700140// withIdentity authenticates the user against the provider's identity service, and provides an
141// accessor for additional services.
Samuel A. Falvo II887d7802013-07-29 17:55:37 -0700142func withIdentity(ar bool, f func(gophercloud.AccessProvider)) {
Rafael Garciae4a550e2013-12-06 17:00:32 -0300143 provider, username, password, _ := getCredentials()
Samuel A. Falvo IIf722dbf2013-07-29 15:44:30 -0700144 acc, err := gophercloud.Authenticate(
145 provider,
146 gophercloud.AuthOptions{
Mark Peekd27e2532013-08-27 07:58:09 -0700147 Username: username,
148 Password: password,
Samuel A. Falvo II887d7802013-07-29 17:55:37 -0700149 AllowReauth: ar,
Samuel A. Falvo IIf722dbf2013-07-29 15:44:30 -0700150 },
151 )
152 if err != nil {
153 panic(err)
154 }
155
156 f(acc)
157}
158
159// withServerApi acquires the cloud servers API.
160func withServerApi(acc gophercloud.AccessProvider, f func(gophercloud.CloudServersProvider)) {
161 api, err := gophercloud.ServersApi(acc, gophercloud.ApiCriteria{
162 Name: "cloudServersOpenStack",
Samuel A. Falvo IIf722dbf2013-07-29 15:44:30 -0700163 VersionId: "2",
164 UrlChoice: gophercloud.PublicURL,
165 })
166 if err != nil {
167 panic(err)
168 }
169
170 f(api)
171}
Samuel A. Falvo II002b6512013-07-29 16:30:40 -0700172
173// waitForServerState polls, every 10 seconds, for a given server to appear in the indicated state.
174// This call will block forever if it never appears in the desired state, so if a timeout is required,
175// make sure to call this function in a goroutine.
176func waitForServerState(api gophercloud.CloudServersProvider, id, state string) error {
177 for {
178 s, err := api.ServerById(id)
179 if err != nil {
180 return err
181 }
182 if s.Status == state {
183 return nil
184 }
185 time.Sleep(10 * time.Second)
186 }
187 panic("Impossible")
Mark Peekd27e2532013-08-27 07:58:09 -0700188}
Mark Peekd2188c42013-08-27 08:16:28 -0700189
190// waitForImageState polls, every 10 seconds, for a given image to appear in the indicated state.
191// This call will block forever if it never appears in the desired state, so if a timeout is required,
192// make sure to call this function in a goroutine.
193func waitForImageState(api gophercloud.CloudServersProvider, id, state string) error {
194 for {
195 s, err := api.ImageById(id)
196 if err != nil {
197 return err
198 }
199 if s.Status == state {
200 return nil
201 }
202 time.Sleep(10 * time.Second)
203 }
204 panic("Impossible")
205}