| package main | 
 |  | 
 | import ( | 
 | 	"crypto/rand" | 
 | 	"fmt" | 
 | 	"github.com/rackspace/gophercloud" | 
 | 	"os" | 
 | 	"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 string) { | 
 | 	provider = os.Getenv("SDK_PROVIDER") | 
 | 	username = os.Getenv("SDK_USERNAME") | 
 | 	password = os.Getenv("SDK_PASSWORD") | 
 |  | 
 | 	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 images[i].Id == "23b564c9-c3e6-49f9-bc68-86c7a9ab5018" { | 
 | 			return images[i].Id | 
 | 		} | 
 | 	} | 
 | 	panic("Image 23b564c9-c3e6-49f9-bc68-86c7a9ab5018 (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") | 
 | } |