Merge pull request #41 from rackspace/create-server
Create server
diff --git a/acceptance/04-create-server.go b/acceptance/04-create-server.go
new file mode 100644
index 0000000..bc5ff69
--- /dev/null
+++ b/acceptance/04-create-server.go
@@ -0,0 +1,118 @@
+package main
+
+import (
+ "flag"
+ "fmt"
+ "github.com/rackspace/gophercloud"
+)
+
+var provider, username, password string
+
+var region, serverName, imageRef, flavorRef *string
+var adminPass = flag.String("a", "", "Administrator password (auto-assigned if none)")
+var quiet = flag.Bool("quiet", false, "Quiet mode for acceptance tests. $? non-zero if error.")
+
+func configure() {
+ provider, username, password = getCredentials()
+ region = flag.String("r", "DFW", "Rackspace region in which to create the server")
+ serverName = flag.String("n", randomString(16), "Server name (what you see in the control panel)")
+ imageRef = flag.String("i", "", "ID of image to deploy onto the server")
+ flavorRef = flag.String("f", "", "Flavor of server to deploy image upon")
+
+ flag.Parse()
+}
+
+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 == "6a668bb8-fb5d-407a-9a89-6f957bced767" {
+ return images[i].Id
+ }
+ }
+ panic("Image 6a668bb8-fb5d-407a-9a89-6f957bced767 (Ubuntu 12.04 LTS) not found.")
+}
+
+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.")
+}
+
+func main() {
+ configure()
+
+ auth, err := gophercloud.Authenticate(
+ provider,
+ gophercloud.AuthOptions{
+ Username: username,
+ Password: password,
+ },
+ )
+ if err != nil {
+ panic(err)
+ }
+
+ servers, err := gophercloud.ServersApi(auth, gophercloud.ApiCriteria{
+ Name: "cloudServersOpenStack",
+ Region: *region,
+ VersionId: "2",
+ UrlChoice: gophercloud.PublicURL,
+ })
+ if err != nil {
+ panic(err)
+ }
+
+ if *imageRef == "" {
+ *imageRef = aSuitableImage(servers)
+ }
+
+ if *flavorRef == "" {
+ *flavorRef = aSuitableFlavor(servers)
+ }
+
+ _, err = servers.CreateServer(gophercloud.NewServer{
+ Name: *serverName,
+ ImageRef: *imageRef,
+ FlavorRef: *flavorRef,
+ AdminPass: *adminPass,
+ })
+ if err != nil {
+ panic(err)
+ }
+
+ allServers, err := servers.ListServers()
+ if err != nil {
+ panic(err)
+ }
+
+ if !*quiet {
+ fmt.Printf("ID,Name,Status,Progress\n")
+ for _, i := range allServers {
+ fmt.Printf("%s,\"%s\",%s,%d\n", i.Id, i.Name, i.Status, i.Progress)
+ }
+ }
+}
diff --git a/acceptance/libargs.go b/acceptance/libargs.go
index 7b50f46..0af62d4 100644
--- a/acceptance/libargs.go
+++ b/acceptance/libargs.go
@@ -3,6 +3,7 @@
import (
"fmt"
"os"
+ "crypto/rand"
)
// getCredentials will verify existence of needed credential information
@@ -23,3 +24,17 @@
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(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 string(bytes)
+}
\ No newline at end of file
diff --git a/common_types.go b/common_types.go
index 4e3d32c..044b308 100644
--- a/common_types.go
+++ b/common_types.go
@@ -7,3 +7,18 @@
Rel string `json:"rel"`
Type string `json:"type"`
}
+
+// FileConfig structures represent a blob of data which must appear at a
+// a specific location in a server's filesystem. The file contents are
+// base-64 encoded.
+type FileConfig struct {
+ Path string `json:"path"`
+ Contents string `json:"contents"`
+}
+
+// NetworkConfig structures represent an affinity between a server and a
+// specific, uniquely identified network. Networks are identified through
+// universally unique IDs.
+type NetworkConfig struct {
+ Uuid string `json:"uuid"`
+}
diff --git a/interfaces.go b/interfaces.go
index 649e756..b50fdf3 100644
--- a/interfaces.go
+++ b/interfaces.go
@@ -22,6 +22,7 @@
ListServers() ([]Server, error)
ServerById(id string) (*Server, error)
+ CreateServer(ns NewServer) (*NewServer, error)
// Images
diff --git a/servers.go b/servers.go
index ee67823..29a1fc5 100644
--- a/servers.go
+++ b/servers.go
@@ -51,6 +51,24 @@
return s, err
}
+// See the CloudServersProvider interface for details.
+func (gsp *genericServersProvider) CreateServer(ns NewServer) (*NewServer, error) {
+ var s *NewServer
+
+ ep := gsp.endpoint + "/servers"
+ err := perigee.Post(ep, perigee.Options{
+ ReqBody: &struct {
+ Server *NewServer `json:"server"`
+ }{&ns},
+ Results: &struct{ Server **NewServer }{&s},
+ MoreHeaders: map[string]string{
+ "X-Auth-Token": gsp.access.AuthToken(),
+ },
+ OkCodes: []int{202},
+ })
+ return s, err
+}
+
// RaxBandwidth provides measurement of server bandwidth consumed over a given audit interval.
type RaxBandwidth struct {
AuditPeriodEnd string `json:"audit_period_end"`
@@ -175,3 +193,61 @@
OsExtStsTaskState string `json:"OS-EXT-STS:task_state"`
OsExtStsVmState string `json:"OS-EXT-STS:vm_state"`
}
+
+// NewServer structures are used for both requests and responses.
+// The fields discussed below are relevent for server-creation purposes.
+//
+// The Name field contains the desired name of the server.
+// Note that (at present) Rackspace permits more than one server with the same name;
+// however, software should not depend on this.
+// Not only will Rackspace support thank you, so will your own devops engineers.
+// A name is required.
+//
+// The ImageRef field contains the ID of the desired software image to place on the server.
+// This ID must be found in the image slice returned by the Images() function.
+// This field is required.
+//
+// The FlavorRef field contains the ID of the server configuration desired for deployment.
+// This ID must be found in the flavor slice returned by the Flavors() function.
+// This field is required.
+//
+// For OsDcfDiskConfig, refer to the Image or Server structure documentation.
+// This field defaults to "AUTO" if not explicitly provided.
+//
+// Metadata contains a small key/value association of arbitrary data.
+// Neither Rackspace nor OpenStack places significance on this field in any way.
+// This field defaults to an empty map if not provided.
+//
+// Personality specifies the contents of certain files in the server's filesystem.
+// The files and their contents are mapped through a slice of FileConfig structures.
+// If not provided, all filesystem entities retain their image-specific configuration.
+//
+// Networks specifies an affinity for the server's various networks and interfaces.
+// Networks are identified through UUIDs; see NetworkConfig structure documentation for more details.
+// If not provided, network affinity is determined automatically.
+//
+// The AdminPass field may be used to provide a root- or administrator-password
+// during the server provisioning process.
+// If not provided, a random password will be automatically generated and returned in this field.
+//
+// The following fields are intended to be used to communicate certain results about the server being provisioned.
+// When attempting to create a new server, these fields MUST not be provided.
+// They'll be filled in by the response received from the Rackspace APIs.
+//
+// The Id field contains the server's unique identifier.
+// The identifier's scope is best assumed to be bound by the user's account, unless other arrangements have been made with Rackspace.
+//
+// Any Links provided are used to refer to the server specifically by URL.
+// These links are useful for making additional REST calls not explicitly supported by Gorax.
+type NewServer struct {
+ Name string `json:"name",omitempty`
+ ImageRef string `json:"imageRef,omitempty"`
+ FlavorRef string `json:"flavorRef,omitempty"`
+ Metadata interface{} `json:"metadata,omitempty"`
+ Personality []FileConfig `json:"personality,omitempty"`
+ Networks []NetworkConfig `json:"networks,omitempty"`
+ AdminPass string `json:"adminPass,omitempty"`
+ Id string `json:"id,omitempty"`
+ Links []Link `json:"links,omitempty"`
+ OsDcfDiskConfig string `json:"OS-DCF:diskConfig,omitempty"`
+}