blob: 0c1d5391fc3c5923461c7dfc25b55212df2b278f [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"
Max Lincoln5b250e02013-12-13 14:49:38 -03008 "strings"
Samuel A. Falvo II002b6512013-07-29 16:30:40 -07009 "time"
Samuel A. Falvo II704a7502013-07-10 15:23:43 -070010)
11
12// getCredentials will verify existence of needed credential information
13// provided through environment variables. This function will not return
14// if at least one piece of required information is missing.
Rafael Garciae4a550e2013-12-06 17:00:32 -030015func getCredentials() (provider, username, password, apiKey string) {
Samuel A. Falvo II704a7502013-07-10 15:23:43 -070016 provider = os.Getenv("SDK_PROVIDER")
17 username = os.Getenv("SDK_USERNAME")
18 password = os.Getenv("SDK_PASSWORD")
Rafael Garcia752cb332013-12-12 22:16:58 -030019 apiKey = os.Getenv("SDK_API_KEY")
Samuel A. Falvo II704a7502013-07-10 15:23:43 -070020
21 if (provider == "") || (username == "") || (password == "") {
22 fmt.Fprintf(os.Stderr, "One or more of the following environment variables aren't set:\n")
23 fmt.Fprintf(os.Stderr, " SDK_PROVIDER=\"%s\"\n", provider)
24 fmt.Fprintf(os.Stderr, " SDK_USERNAME=\"%s\"\n", username)
25 fmt.Fprintf(os.Stderr, " SDK_PASSWORD=\"%s\"\n", password)
26 os.Exit(1)
27 }
28
29 return
30}
Samuel A. Falvo IIe91ff6d2013-07-11 15:46:10 -070031
32// randomString generates a string of given length, but random content.
33// All content will be within the ASCII graphic character set.
34// (Implementation from Even Shaw's contribution on
35// 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 -070036func randomString(prefix string, n int) string {
Mark Peekd27e2532013-08-27 07:58:09 -070037 const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
38 var bytes = make([]byte, n)
39 rand.Read(bytes)
40 for i, b := range bytes {
41 bytes[i] = alphanum[b%byte(len(alphanum))]
42 }
43 return prefix + string(bytes)
Samuel A. Falvo II8935ca32013-07-25 21:27:06 -070044}
45
46// aSuitableImage finds a minimal image for use in dynamically creating servers.
47// If none can be found, this function will panic.
48func aSuitableImage(api gophercloud.CloudServersProvider) string {
49 images, err := api.ListImages()
50 if err != nil {
51 panic(err)
52 }
53
54 // TODO(sfalvo):
55 // Works for Rackspace, might not work for your provider!
56 // Need to figure out why ListImages() provides 0 values for
57 // Ram and Disk fields.
58 //
59 // Until then, just return Ubuntu 12.04 LTS.
60 for i := 0; i < len(images); i++ {
Max Lincoln5b250e02013-12-13 14:49:38 -030061 if strings.Contains(images[i].Name, "Ubuntu 12.04 LTS") {
Samuel A. Falvo II8935ca32013-07-25 21:27:06 -070062 return images[i].Id
63 }
64 }
Max Lincoln5b250e02013-12-13 14:49:38 -030065 panic("Image for Ubuntu 12.04 LTS not found.")
Samuel A. Falvo II8935ca32013-07-25 21:27:06 -070066}
67
68// aSuitableFlavor finds the minimum flavor capable of running the test image
69// chosen by aSuitableImage. If none can be found, this function will panic.
70func aSuitableFlavor(api gophercloud.CloudServersProvider) string {
71 flavors, err := api.ListFlavors()
72 if err != nil {
73 panic(err)
74 }
75
76 // TODO(sfalvo):
77 // Works for Rackspace, might not work for your provider!
78 // Need to figure out why ListFlavors() provides 0 values for
79 // Ram and Disk fields.
80 //
81 // Until then, just return Ubuntu 12.04 LTS.
82 for i := 0; i < len(flavors); i++ {
83 if flavors[i].Id == "2" {
84 return flavors[i].Id
85 }
86 }
87 panic("Flavor 2 (512MB 1-core 20GB machine) not found.")
88}
89
90// createServer creates a new server in a manner compatible with acceptance testing.
91// In particular, it ensures that the name of the server always starts with "ACPTTEST--",
92// which the delete servers acceptance test relies on to identify servers to delete.
93// Passing in empty image and flavor references will force the use of reasonable defaults.
94// An empty name string will result in a dynamically created name prefixed with "ACPTTEST--".
95// A blank admin password will cause a password to be automatically generated; however,
96// at present no means of recovering this password exists, as no acceptance tests yet require
97// this data.
Samuel A. Falvo II80699602013-07-25 23:35:57 -070098func createServer(servers gophercloud.CloudServersProvider, imageRef, flavorRef, name, adminPass string) (string, error) {
Samuel A. Falvo II8935ca32013-07-25 21:27:06 -070099 if imageRef == "" {
100 imageRef = aSuitableImage(servers)
101 }
102
103 if flavorRef == "" {
104 flavorRef = aSuitableFlavor(servers)
105 }
106
107 if len(name) < 1 {
108 name = randomString("ACPTTEST", 16)
109 }
110
111 if (len(name) < 8) || (name[0:8] != "ACPTTEST") {
112 name = fmt.Sprintf("ACPTTEST--%s", name)
113 }
114
Samuel A. Falvo II80699602013-07-25 23:35:57 -0700115 newServer, err := servers.CreateServer(gophercloud.NewServer{
Samuel A. Falvo II8935ca32013-07-25 21:27:06 -0700116 Name: name,
117 ImageRef: imageRef,
118 FlavorRef: flavorRef,
119 AdminPass: adminPass,
120 })
121
Samuel A. Falvo II80699602013-07-25 23:35:57 -0700122 if err != nil {
123 return "", err
124 }
125
126 return newServer.Id, nil
Samuel A. Falvo II8935ca32013-07-25 21:27:06 -0700127}
Samuel A. Falvo II8512e9a2013-07-26 22:53:29 -0700128
129// findAlternativeFlavor locates a flavor to resize a server to. It is guaranteed to be different
130// than what aSuitableFlavor() returns. If none could be found, this function will panic.
131func findAlternativeFlavor() string {
Mark Peekd27e2532013-08-27 07:58:09 -0700132 return "3" // 1GB image, up from 512MB image
Samuel A. Falvo IIf722dbf2013-07-29 15:44:30 -0700133}
134
Samuel A. Falvo II414c15c2013-08-01 15:16:46 -0700135// findAlternativeImage locates an image to resize or rebuild a server with. It is guaranteed to be
136// different than what aSuitableImage() returns. If none could be found, this function will panic.
137func findAlternativeImage() string {
138 return "c6f9c411-e708-4952-91e5-62ded5ea4d3e"
139}
140
Samuel A. Falvo IIf722dbf2013-07-29 15:44:30 -0700141// withIdentity authenticates the user against the provider's identity service, and provides an
142// accessor for additional services.
Samuel A. Falvo II887d7802013-07-29 17:55:37 -0700143func withIdentity(ar bool, f func(gophercloud.AccessProvider)) {
Rafael Garciae4a550e2013-12-06 17:00:32 -0300144 provider, username, password, _ := getCredentials()
Samuel A. Falvo IIf722dbf2013-07-29 15:44:30 -0700145 acc, err := gophercloud.Authenticate(
146 provider,
147 gophercloud.AuthOptions{
Mark Peekd27e2532013-08-27 07:58:09 -0700148 Username: username,
149 Password: password,
Samuel A. Falvo II887d7802013-07-29 17:55:37 -0700150 AllowReauth: ar,
Samuel A. Falvo IIf722dbf2013-07-29 15:44:30 -0700151 },
152 )
153 if err != nil {
154 panic(err)
155 }
156
157 f(acc)
158}
159
160// withServerApi acquires the cloud servers API.
161func withServerApi(acc gophercloud.AccessProvider, f func(gophercloud.CloudServersProvider)) {
162 api, err := gophercloud.ServersApi(acc, gophercloud.ApiCriteria{
163 Name: "cloudServersOpenStack",
Samuel A. Falvo IIf722dbf2013-07-29 15:44:30 -0700164 VersionId: "2",
165 UrlChoice: gophercloud.PublicURL,
166 })
167 if err != nil {
168 panic(err)
169 }
170
171 f(api)
172}
Samuel A. Falvo II002b6512013-07-29 16:30:40 -0700173
174// waitForServerState polls, every 10 seconds, for a given server to appear in the indicated state.
175// This call will block forever if it never appears in the desired state, so if a timeout is required,
176// make sure to call this function in a goroutine.
177func waitForServerState(api gophercloud.CloudServersProvider, id, state string) error {
178 for {
179 s, err := api.ServerById(id)
180 if err != nil {
181 return err
182 }
183 if s.Status == state {
184 return nil
185 }
186 time.Sleep(10 * time.Second)
187 }
188 panic("Impossible")
Mark Peekd27e2532013-08-27 07:58:09 -0700189}
Mark Peekd2188c42013-08-27 08:16:28 -0700190
191// waitForImageState polls, every 10 seconds, for a given image to appear in the indicated state.
192// This call will block forever if it never appears in the desired state, so if a timeout is required,
193// make sure to call this function in a goroutine.
194func waitForImageState(api gophercloud.CloudServersProvider, id, state string) error {
195 for {
196 s, err := api.ImageById(id)
197 if err != nil {
198 return err
199 }
200 if s.Status == state {
201 return nil
202 }
203 time.Sleep(10 * time.Second)
204 }
205 panic("Impossible")
206}