| package main |
| |
| import ( |
| "crypto/rand" |
| "fmt" |
| "github.com/rackspace/gophercloud" |
| "os" |
| "strings" |
| "time" |
| ) |
| |
| // getCredentials will verify existence of needed credential information |
| // provided through environment variables. This function will not return |
| // if at least one piece of required information is missing. |
| func getCredentials() (provider, username, password, apiKey string) { |
| provider = os.Getenv("SDK_PROVIDER") |
| username = os.Getenv("SDK_USERNAME") |
| password = os.Getenv("SDK_PASSWORD") |
| apiKey = os.Getenv("SDK_API_KEY") |
| |
| if (provider == "") || (username == "") || (password == "") { |
| fmt.Fprintf(os.Stderr, "One or more of the following environment variables aren't set:\n") |
| fmt.Fprintf(os.Stderr, " SDK_PROVIDER=\"%s\"\n", provider) |
| fmt.Fprintf(os.Stderr, " SDK_USERNAME=\"%s\"\n", username) |
| fmt.Fprintf(os.Stderr, " SDK_PASSWORD=\"%s\"\n", password) |
| os.Exit(1) |
| } |
| |
| return |
| } |
| |
| // 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) |
| } |
| |
| // aSuitableImage finds a minimal image for use in dynamically creating servers. |
| // If none can be found, this function will panic. |
| func aSuitableImage(api gophercloud.CloudServersProvider) string { |
| images, err := api.ListImages() |
| if err != nil { |
| panic(err) |
| } |
| |
| // TODO(sfalvo): |
| // Works for Rackspace, might not work for your provider! |
| // Need to figure out why ListImages() provides 0 values for |
| // Ram and Disk fields. |
| // |
| // Until then, just return Ubuntu 12.04 LTS. |
| for i := 0; i < len(images); i++ { |
| if strings.Contains(images[i].Name, "Ubuntu 12.04 LTS") { |
| return images[i].Id |
| } |
| } |
| panic("Image for Ubuntu 12.04 LTS not found.") |
| } |
| |
| // aSuitableFlavor finds the minimum flavor capable of running the test image |
| // chosen by aSuitableImage. If none can be found, this function will panic. |
| func aSuitableFlavor(api gophercloud.CloudServersProvider) string { |
| flavors, err := api.ListFlavors() |
| if err != nil { |
| panic(err) |
| } |
| |
| // TODO(sfalvo): |
| // Works for Rackspace, might not work for your provider! |
| // Need to figure out why ListFlavors() provides 0 values for |
| // Ram and Disk fields. |
| // |
| // Until then, just return Ubuntu 12.04 LTS. |
| for i := 0; i < len(flavors); i++ { |
| if flavors[i].Id == "2" { |
| return flavors[i].Id |
| } |
| } |
| panic("Flavor 2 (512MB 1-core 20GB machine) not found.") |
| } |
| |
| // createServer creates a new server in a manner compatible with acceptance testing. |
| // In particular, it ensures that the name of the server always starts with "ACPTTEST--", |
| // which the delete servers acceptance test relies on to identify servers to delete. |
| // Passing in empty image and flavor references will force the use of reasonable defaults. |
| // An empty name string will result in a dynamically created name prefixed with "ACPTTEST--". |
| // A blank admin password will cause a password to be automatically generated; however, |
| // at present no means of recovering this password exists, as no acceptance tests yet require |
| // this data. |
| func createServer(servers gophercloud.CloudServersProvider, imageRef, flavorRef, name, adminPass string) (string, error) { |
| if imageRef == "" { |
| imageRef = aSuitableImage(servers) |
| } |
| |
| if flavorRef == "" { |
| flavorRef = aSuitableFlavor(servers) |
| } |
| |
| if len(name) < 1 { |
| name = randomString("ACPTTEST", 16) |
| } |
| |
| if (len(name) < 8) || (name[0:8] != "ACPTTEST") { |
| name = fmt.Sprintf("ACPTTEST--%s", name) |
| } |
| |
| newServer, err := servers.CreateServer(gophercloud.NewServer{ |
| Name: name, |
| ImageRef: imageRef, |
| FlavorRef: flavorRef, |
| AdminPass: adminPass, |
| }) |
| |
| if err != nil { |
| return "", err |
| } |
| |
| return newServer.Id, nil |
| } |
| |
| // findAlternativeFlavor locates a flavor to resize a server to. It is guaranteed to be different |
| // than what aSuitableFlavor() returns. If none could be found, this function will panic. |
| func findAlternativeFlavor() string { |
| return "3" // 1GB image, up from 512MB image |
| } |
| |
| // findAlternativeImage locates an image to resize or rebuild a server with. It is guaranteed to be |
| // different than what aSuitableImage() returns. If none could be found, this function will panic. |
| func findAlternativeImage() string { |
| return "c6f9c411-e708-4952-91e5-62ded5ea4d3e" |
| } |
| |
| // withIdentity authenticates the user against the provider's identity service, and provides an |
| // accessor for additional services. |
| func withIdentity(ar bool, f func(gophercloud.AccessProvider)) { |
| provider, username, password, _ := getCredentials() |
| acc, err := gophercloud.Authenticate( |
| provider, |
| gophercloud.AuthOptions{ |
| Username: username, |
| Password: password, |
| AllowReauth: ar, |
| }, |
| ) |
| if err != nil { |
| panic(err) |
| } |
| |
| f(acc) |
| } |
| |
| // withServerApi acquires the cloud servers API. |
| func withServerApi(acc gophercloud.AccessProvider, f func(gophercloud.CloudServersProvider)) { |
| api, err := gophercloud.ServersApi(acc, gophercloud.ApiCriteria{ |
| Name: "cloudServersOpenStack", |
| VersionId: "2", |
| UrlChoice: gophercloud.PublicURL, |
| }) |
| if err != nil { |
| panic(err) |
| } |
| |
| f(api) |
| } |
| |
| // waitForServerState polls, every 10 seconds, for a given server to appear in the indicated state. |
| // This call will block forever if it never appears in the desired state, so if a timeout is required, |
| // make sure to call this function in a goroutine. |
| func waitForServerState(api gophercloud.CloudServersProvider, id, state string) error { |
| for { |
| s, err := api.ServerById(id) |
| if err != nil { |
| return err |
| } |
| if s.Status == state { |
| return nil |
| } |
| time.Sleep(10 * time.Second) |
| } |
| panic("Impossible") |
| } |
| |
| // waitForImageState polls, every 10 seconds, for a given image to appear in the indicated state. |
| // This call will block forever if it never appears in the desired state, so if a timeout is required, |
| // make sure to call this function in a goroutine. |
| func waitForImageState(api gophercloud.CloudServersProvider, id, state string) error { |
| for { |
| s, err := api.ImageById(id) |
| if err != nil { |
| return err |
| } |
| if s.Status == state { |
| return nil |
| } |
| time.Sleep(10 * time.Second) |
| } |
| panic("Impossible") |
| } |