blob: f9b56ea1dddf7ccaae727753a18233e4c145d5f6 [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.
14func getCredentials() (provider, username, password string) {
15 provider = os.Getenv("SDK_PROVIDER")
16 username = os.Getenv("SDK_USERNAME")
17 password = os.Getenv("SDK_PASSWORD")
18
19 if (provider == "") || (username == "") || (password == "") {
20 fmt.Fprintf(os.Stderr, "One or more of the following environment variables aren't set:\n")
21 fmt.Fprintf(os.Stderr, " SDK_PROVIDER=\"%s\"\n", provider)
22 fmt.Fprintf(os.Stderr, " SDK_USERNAME=\"%s\"\n", username)
23 fmt.Fprintf(os.Stderr, " SDK_PASSWORD=\"%s\"\n", password)
24 os.Exit(1)
25 }
26
27 return
28}
Samuel A. Falvo IIe91ff6d2013-07-11 15:46:10 -070029
30// randomString generates a string of given length, but random content.
31// All content will be within the ASCII graphic character set.
32// (Implementation from Even Shaw's contribution on
33// 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 -070034func randomString(prefix string, n int) string {
Mark Peekd27e2532013-08-27 07:58:09 -070035 const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
36 var bytes = make([]byte, n)
37 rand.Read(bytes)
38 for i, b := range bytes {
39 bytes[i] = alphanum[b%byte(len(alphanum))]
40 }
41 return prefix + string(bytes)
Samuel A. Falvo II8935ca32013-07-25 21:27:06 -070042}
43
44// aSuitableImage finds a minimal image for use in dynamically creating servers.
45// If none can be found, this function will panic.
46func aSuitableImage(api gophercloud.CloudServersProvider) string {
47 images, err := api.ListImages()
48 if err != nil {
49 panic(err)
50 }
51
52 // TODO(sfalvo):
53 // Works for Rackspace, might not work for your provider!
54 // Need to figure out why ListImages() provides 0 values for
55 // Ram and Disk fields.
56 //
57 // Until then, just return Ubuntu 12.04 LTS.
58 for i := 0; i < len(images); i++ {
59 if images[i].Id == "23b564c9-c3e6-49f9-bc68-86c7a9ab5018" {
60 return images[i].Id
61 }
62 }
63 panic("Image 23b564c9-c3e6-49f9-bc68-86c7a9ab5018 (Ubuntu 12.04 LTS) not found.")
64}
65
66// aSuitableFlavor finds the minimum flavor capable of running the test image
67// chosen by aSuitableImage. If none can be found, this function will panic.
68func aSuitableFlavor(api gophercloud.CloudServersProvider) string {
69 flavors, err := api.ListFlavors()
70 if err != nil {
71 panic(err)
72 }
73
74 // TODO(sfalvo):
75 // Works for Rackspace, might not work for your provider!
76 // Need to figure out why ListFlavors() provides 0 values for
77 // Ram and Disk fields.
78 //
79 // Until then, just return Ubuntu 12.04 LTS.
80 for i := 0; i < len(flavors); i++ {
81 if flavors[i].Id == "2" {
82 return flavors[i].Id
83 }
84 }
85 panic("Flavor 2 (512MB 1-core 20GB machine) not found.")
86}
87
88// createServer creates a new server in a manner compatible with acceptance testing.
89// In particular, it ensures that the name of the server always starts with "ACPTTEST--",
90// which the delete servers acceptance test relies on to identify servers to delete.
91// Passing in empty image and flavor references will force the use of reasonable defaults.
92// An empty name string will result in a dynamically created name prefixed with "ACPTTEST--".
93// A blank admin password will cause a password to be automatically generated; however,
94// at present no means of recovering this password exists, as no acceptance tests yet require
95// this data.
Samuel A. Falvo II80699602013-07-25 23:35:57 -070096func createServer(servers gophercloud.CloudServersProvider, imageRef, flavorRef, name, adminPass string) (string, error) {
Samuel A. Falvo II8935ca32013-07-25 21:27:06 -070097 if imageRef == "" {
98 imageRef = aSuitableImage(servers)
99 }
100
101 if flavorRef == "" {
102 flavorRef = aSuitableFlavor(servers)
103 }
104
105 if len(name) < 1 {
106 name = randomString("ACPTTEST", 16)
107 }
108
109 if (len(name) < 8) || (name[0:8] != "ACPTTEST") {
110 name = fmt.Sprintf("ACPTTEST--%s", name)
111 }
112
Samuel A. Falvo II80699602013-07-25 23:35:57 -0700113 newServer, err := servers.CreateServer(gophercloud.NewServer{
Samuel A. Falvo II8935ca32013-07-25 21:27:06 -0700114 Name: name,
115 ImageRef: imageRef,
116 FlavorRef: flavorRef,
117 AdminPass: adminPass,
118 })
119
Samuel A. Falvo II80699602013-07-25 23:35:57 -0700120 if err != nil {
121 return "", err
122 }
123
124 return newServer.Id, nil
Samuel A. Falvo II8935ca32013-07-25 21:27:06 -0700125}
Samuel A. Falvo II8512e9a2013-07-26 22:53:29 -0700126
127// findAlternativeFlavor locates a flavor to resize a server to. It is guaranteed to be different
128// than what aSuitableFlavor() returns. If none could be found, this function will panic.
129func findAlternativeFlavor() string {
Mark Peekd27e2532013-08-27 07:58:09 -0700130 return "3" // 1GB image, up from 512MB image
Samuel A. Falvo IIf722dbf2013-07-29 15:44:30 -0700131}
132
Samuel A. Falvo II414c15c2013-08-01 15:16:46 -0700133// findAlternativeImage locates an image to resize or rebuild a server with. It is guaranteed to be
134// different than what aSuitableImage() returns. If none could be found, this function will panic.
135func findAlternativeImage() string {
136 return "c6f9c411-e708-4952-91e5-62ded5ea4d3e"
137}
138
Samuel A. Falvo IIf722dbf2013-07-29 15:44:30 -0700139// withIdentity authenticates the user against the provider's identity service, and provides an
140// accessor for additional services.
Samuel A. Falvo II887d7802013-07-29 17:55:37 -0700141func withIdentity(ar bool, f func(gophercloud.AccessProvider)) {
Samuel A. Falvo IIf722dbf2013-07-29 15:44:30 -0700142 provider, username, password := getCredentials()
143 acc, err := gophercloud.Authenticate(
144 provider,
145 gophercloud.AuthOptions{
Mark Peekd27e2532013-08-27 07:58:09 -0700146 Username: username,
147 Password: password,
Samuel A. Falvo II887d7802013-07-29 17:55:37 -0700148 AllowReauth: ar,
Samuel A. Falvo IIf722dbf2013-07-29 15:44:30 -0700149 },
150 )
151 if err != nil {
152 panic(err)
153 }
154
155 f(acc)
156}
157
158// withServerApi acquires the cloud servers API.
159func withServerApi(acc gophercloud.AccessProvider, f func(gophercloud.CloudServersProvider)) {
160 api, err := gophercloud.ServersApi(acc, gophercloud.ApiCriteria{
161 Name: "cloudServersOpenStack",
162 Region: "DFW",
163 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}