blob: 8ac26733e0b89f80010d7d9a0ec3517debe1da09 [file] [log] [blame]
Samuel A. Falvo IIc007c272014-02-10 20:49:26 -08001package servers
2
3import (
Ash Wilson6a310e02014-09-29 08:24:02 -04004 "encoding/base64"
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -07005 "fmt"
Ash Wilson01626a32014-09-17 10:38:07 -04006
Jon Perritt30558642014-04-14 17:07:12 -05007 "github.com/racker/perigee"
Ash Wilson01626a32014-09-17 10:38:07 -04008 "github.com/rackspace/gophercloud"
9 "github.com/rackspace/gophercloud/pagination"
Samuel A. Falvo IIc007c272014-02-10 20:49:26 -080010)
11
Samuel A. Falvo IIc007c272014-02-10 20:49:26 -080012// List makes a request against the API to list servers accessible to you.
Ash Wilson01626a32014-09-17 10:38:07 -040013func List(client *gophercloud.ServiceClient) pagination.Pager {
14 createPage := func(r pagination.LastHTTPResponse) pagination.Page {
Ash Wilson397c78b2014-09-25 15:19:14 -040015 return ServerPage{pagination.LinkedPageBase{LastHTTPResponse: r}}
Samuel A. Falvo IIc007c272014-02-10 20:49:26 -080016 }
17
Ash Wilson31f6bde2014-09-25 14:52:12 -040018 return pagination.NewPager(client, detailURL(client), createPage)
Samuel A. Falvo IIc007c272014-02-10 20:49:26 -080019}
20
Ash Wilson6a310e02014-09-29 08:24:02 -040021// CreateOptsLike describes struct types that can be accepted by the Create call.
22// The CreateOpts struct in this package does.
23type CreateOptsLike interface {
24 ToServerCreateReqData() map[string]interface{}
25}
26
27// Network is used within CreateOpts to control a new server's network attachments.
28type Network struct {
29 // UUID of a nova-network to attach to the newly provisioned server.
30 // Required unless Port is provided.
31 UUID string
32
33 // Port of a neutron network to attach to the newly provisioned server.
34 // Required unless UUID is provided.
35 Port string
36
37 // FixedIP [optional] specifies a fixed IPv4 address to be used on this network.
38 FixedIP string
39}
40
41// CreateOpts specifies server creation parameters.
42type CreateOpts struct {
43 // Name [required] is the name to assign to the newly launched server.
44 Name string
45
46 // ImageRef [required] is the ID or full URL to the image that contains the server's OS and initial state.
47 // Optional if using the boot-from-volume extension.
48 ImageRef string
49
50 // FlavorRef [required] is the ID or full URL to the flavor that describes the server's specs.
51 FlavorRef string
52
53 // SecurityGroups [optional] lists the names of the security groups to which this server should belong.
54 SecurityGroups []string
55
56 // UserData [optional] contains configuration information or scripts to use upon launch.
57 // Create will base64-encode it for you.
58 UserData []byte
59
60 // AvailabilityZone [optional] in which to launch the server.
61 AvailabilityZone string
62
63 // Networks [optional] dictates how this server will be attached to available networks.
64 // By default, the server will be attached to all isolated networks for the tenant.
65 Networks []Network
66
67 // Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the server.
68 Metadata map[string]string
69
70 // Personality [optional] includes the path and contents of a file to inject into the server at launch.
71 // The maximum size of the file is 255 bytes (decoded).
72 Personality []byte
73
74 // ConfigDrive [optional] enables metadata injection through a configuration drive.
75 ConfigDrive bool
76}
77
78// ToServerCreateReqData assembles a request body based on the contents of a CreateOpts.
79func (opts CreateOpts) ToServerCreateReqData() map[string]interface{} {
80 server := make(map[string]interface{})
81
82 server["name"] = opts.Name
83 server["imageRef"] = opts.ImageRef
84 server["flavorRef"] = opts.FlavorRef
85
86 if opts.UserData != nil {
87 encoded := base64.StdEncoding.EncodeToString(opts.UserData)
88 server["user_data"] = &encoded
89 }
90 if opts.Personality != nil {
91 encoded := base64.StdEncoding.EncodeToString(opts.Personality)
92 server["personality"] = &encoded
93 }
94 if opts.ConfigDrive {
95 server["config_drive"] = "true"
96 }
97 if opts.AvailabilityZone != "" {
98 server["availability_zone"] = opts.AvailabilityZone
99 }
100 if opts.Metadata != nil {
101 server["metadata"] = opts.Metadata
102 }
103
104 if len(opts.SecurityGroups) > 0 {
105 securityGroups := make([]map[string]interface{}, len(opts.SecurityGroups))
106 for i, groupName := range opts.SecurityGroups {
107 securityGroups[i] = map[string]interface{}{"name": groupName}
108 }
109 }
110 if len(opts.Networks) > 0 {
111 networks := make([]map[string]interface{}, len(opts.Networks))
112 for i, net := range opts.Networks {
113 networks[i] = make(map[string]interface{})
114 if net.UUID != "" {
115 networks[i]["uuid"] = net.UUID
116 }
117 if net.Port != "" {
118 networks[i]["port"] = net.Port
119 }
120 if net.FixedIP != "" {
121 networks[i]["fixed_ip"] = net.FixedIP
122 }
123 }
124 }
125
126 return map[string]interface{}{"server": server}
127}
128
Samuel A. Falvo IIce000732014-02-13 18:53:53 -0800129// Create requests a server to be provisioned to the user in the current tenant.
Ash Wilson6a310e02014-09-29 08:24:02 -0400130func Create(client *gophercloud.ServiceClient, opts CreateOptsLike) CreateResult {
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400131 var result CreateResult
Ash Wilson31f6bde2014-09-25 14:52:12 -0400132 _, result.Err = perigee.Request("POST", listURL(client), perigee.Options{
Ash Wilson6a310e02014-09-29 08:24:02 -0400133 Results: &result.Resp,
134 ReqBody: opts.ToServerCreateReqData(),
Ash Wilson01626a32014-09-17 10:38:07 -0400135 MoreHeaders: client.Provider.AuthenticatedHeaders(),
Samuel A. Falvo IIe246ac02014-02-13 23:20:09 -0800136 OkCodes: []int{202},
Samuel A. Falvo IIce000732014-02-13 18:53:53 -0800137 })
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400138 return result
Samuel A. Falvo IIce000732014-02-13 18:53:53 -0800139}
140
141// Delete requests that a server previously provisioned be removed from your account.
Ash Wilson01626a32014-09-17 10:38:07 -0400142func Delete(client *gophercloud.ServiceClient, id string) error {
Ash Wilson31f6bde2014-09-25 14:52:12 -0400143 _, err := perigee.Request("DELETE", serverURL(client, id), perigee.Options{
Ash Wilson01626a32014-09-17 10:38:07 -0400144 MoreHeaders: client.Provider.AuthenticatedHeaders(),
Samuel A. Falvo IIe246ac02014-02-13 23:20:09 -0800145 OkCodes: []int{204},
Samuel A. Falvo IIce000732014-02-13 18:53:53 -0800146 })
147 return err
148}
149
Ash Wilson7ddf0362014-09-17 10:59:09 -0400150// Get requests details on a single server, by ID.
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400151func Get(client *gophercloud.ServiceClient, id string) GetResult {
152 var result GetResult
Ash Wilson31f6bde2014-09-25 14:52:12 -0400153 _, result.Err = perigee.Request("GET", serverURL(client, id), perigee.Options{
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400154 Results: &result.Resp,
Ash Wilson01626a32014-09-17 10:38:07 -0400155 MoreHeaders: client.Provider.AuthenticatedHeaders(),
Samuel A. Falvo IIce000732014-02-13 18:53:53 -0800156 })
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400157 return result
Samuel A. Falvo IIce000732014-02-13 18:53:53 -0800158}
159
Samuel A. Falvo II22d3b772014-02-13 19:27:05 -0800160// Update requests that various attributes of the indicated server be changed.
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400161func Update(client *gophercloud.ServiceClient, id string, opts map[string]interface{}) UpdateResult {
162 var result UpdateResult
Ash Wilson31f6bde2014-09-25 14:52:12 -0400163 _, result.Err = perigee.Request("PUT", serverURL(client, id), perigee.Options{
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400164 Results: &result.Resp,
Samuel A. Falvo II22d3b772014-02-13 19:27:05 -0800165 ReqBody: map[string]interface{}{
166 "server": opts,
167 },
Ash Wilson01626a32014-09-17 10:38:07 -0400168 MoreHeaders: client.Provider.AuthenticatedHeaders(),
Samuel A. Falvo II22d3b772014-02-13 19:27:05 -0800169 })
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400170 return result
Samuel A. Falvo II22d3b772014-02-13 19:27:05 -0800171}
Samuel A. Falvo IIca5f9a32014-03-11 17:52:58 -0700172
Ash Wilson01626a32014-09-17 10:38:07 -0400173// ChangeAdminPassword alters the administrator or root password for a specified server.
174func ChangeAdminPassword(client *gophercloud.ServiceClient, id, newPassword string) error {
Ash Wilsondc7daa82014-09-23 16:34:42 -0400175 var req struct {
176 ChangePassword struct {
177 AdminPass string `json:"adminPass"`
178 } `json:"changePassword"`
179 }
180
181 req.ChangePassword.AdminPass = newPassword
182
Ash Wilson31f6bde2014-09-25 14:52:12 -0400183 _, err := perigee.Request("POST", actionURL(client, id), perigee.Options{
Ash Wilsondc7daa82014-09-23 16:34:42 -0400184 ReqBody: req,
Ash Wilson01626a32014-09-17 10:38:07 -0400185 MoreHeaders: client.Provider.AuthenticatedHeaders(),
Jon Perritt30558642014-04-14 17:07:12 -0500186 OkCodes: []int{202},
Samuel A. Falvo IIca5f9a32014-03-11 17:52:58 -0700187 })
188 return err
189}
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700190
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700191// ErrArgument errors occur when an argument supplied to a package function
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700192// fails to fall within acceptable values. For example, the Reboot() function
193// expects the "how" parameter to be one of HardReboot or SoftReboot. These
194// constants are (currently) strings, leading someone to wonder if they can pass
195// other string values instead, perhaps in an effort to break the API of their
196// provider. Reboot() returns this error in this situation.
197//
198// Function identifies which function was called/which function is generating
199// the error.
200// Argument identifies which formal argument was responsible for producing the
201// error.
202// Value provides the value as it was passed into the function.
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700203type ErrArgument struct {
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700204 Function, Argument string
Jon Perritt30558642014-04-14 17:07:12 -0500205 Value interface{}
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700206}
207
208// Error yields a useful diagnostic for debugging purposes.
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700209func (e *ErrArgument) Error() string {
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700210 return fmt.Sprintf("Bad argument in call to %s, formal parameter %s, value %#v", e.Function, e.Argument, e.Value)
211}
212
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700213func (e *ErrArgument) String() string {
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700214 return e.Error()
215}
216
Ash Wilson01626a32014-09-17 10:38:07 -0400217// RebootMethod describes the mechanisms by which a server reboot can be requested.
218type RebootMethod string
219
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700220// These constants determine how a server should be rebooted.
221// See the Reboot() function for further details.
222const (
Ash Wilson01626a32014-09-17 10:38:07 -0400223 SoftReboot RebootMethod = "SOFT"
224 HardReboot RebootMethod = "HARD"
225 OSReboot = SoftReboot
226 PowerCycle = HardReboot
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700227)
228
229// Reboot requests that a given server reboot.
230// Two methods exist for rebooting a server:
231//
Ash Wilson01626a32014-09-17 10:38:07 -0400232// HardReboot (aka PowerCycle) restarts the server instance by physically cutting power to the machine, or if a VM,
233// terminating it at the hypervisor level.
234// It's done. Caput. Full stop.
235// Then, after a brief while, power is restored or the VM instance restarted.
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700236//
Ash Wilson01626a32014-09-17 10:38:07 -0400237// SoftReboot (aka OSReboot) simply tells the OS to restart under its own procedures.
238// E.g., in Linux, asking it to enter runlevel 6, or executing "sudo shutdown -r now", or by asking Windows to restart the machine.
239func Reboot(client *gophercloud.ServiceClient, id string, how RebootMethod) error {
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700240 if (how != SoftReboot) && (how != HardReboot) {
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700241 return &ErrArgument{
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700242 Function: "Reboot",
243 Argument: "how",
Jon Perritt30558642014-04-14 17:07:12 -0500244 Value: how,
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700245 }
246 }
Jon Perritt30558642014-04-14 17:07:12 -0500247
Ash Wilson31f6bde2014-09-25 14:52:12 -0400248 _, err := perigee.Request("POST", actionURL(client, id), perigee.Options{
Jon Perritt30558642014-04-14 17:07:12 -0500249 ReqBody: struct {
250 C map[string]string `json:"reboot"`
251 }{
Ash Wilson01626a32014-09-17 10:38:07 -0400252 map[string]string{"type": string(how)},
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700253 },
Ash Wilson01626a32014-09-17 10:38:07 -0400254 MoreHeaders: client.Provider.AuthenticatedHeaders(),
Jon Perritt30558642014-04-14 17:07:12 -0500255 OkCodes: []int{202},
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700256 })
257 return err
258}
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700259
Ash Wilson01626a32014-09-17 10:38:07 -0400260// Rebuild requests that the Openstack provider reprovision the server.
261// The rebuild will need to know the server's name and new image reference or ID.
262// In addition, and unlike building a server with Create(), you must provide an administrator password.
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700263//
264// Additional options may be specified with the additional map.
265// This function treats a nil map the same as an empty map.
266//
Ash Wilson01626a32014-09-17 10:38:07 -0400267// Rebuild returns a server result as though you had called GetDetail() on the server's ID.
268// The information, however, refers to the new server, not the old.
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400269func Rebuild(client *gophercloud.ServiceClient, id, name, password, imageRef string, additional map[string]interface{}) RebuildResult {
270 var result RebuildResult
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700271
272 if id == "" {
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400273 result.Err = &ErrArgument{
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700274 Function: "Rebuild",
275 Argument: "id",
Jon Perritt30558642014-04-14 17:07:12 -0500276 Value: "",
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700277 }
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400278 return result
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700279 }
280
281 if name == "" {
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400282 result.Err = &ErrArgument{
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700283 Function: "Rebuild",
284 Argument: "name",
Jon Perritt30558642014-04-14 17:07:12 -0500285 Value: "",
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700286 }
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400287 return result
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700288 }
289
290 if password == "" {
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400291 result.Err = &ErrArgument{
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700292 Function: "Rebuild",
293 Argument: "password",
Jon Perritt30558642014-04-14 17:07:12 -0500294 Value: "",
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700295 }
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400296 return result
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700297 }
298
299 if imageRef == "" {
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400300 result.Err = &ErrArgument{
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700301 Function: "Rebuild",
302 Argument: "imageRef",
Jon Perritt30558642014-04-14 17:07:12 -0500303 Value: "",
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700304 }
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400305 return result
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700306 }
307
308 if additional == nil {
309 additional = make(map[string]interface{}, 0)
310 }
311
312 additional["name"] = name
313 additional["imageRef"] = imageRef
314 additional["adminPass"] = password
315
Ash Wilson31f6bde2014-09-25 14:52:12 -0400316 _, result.Err = perigee.Request("POST", actionURL(client, id), perigee.Options{
Jon Perritt30558642014-04-14 17:07:12 -0500317 ReqBody: struct {
318 R map[string]interface{} `json:"rebuild"`
319 }{
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700320 additional,
321 },
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400322 Results: &result.Resp,
Ash Wilson01626a32014-09-17 10:38:07 -0400323 MoreHeaders: client.Provider.AuthenticatedHeaders(),
Jon Perritt30558642014-04-14 17:07:12 -0500324 OkCodes: []int{202},
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700325 })
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400326 return result
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700327}
328
329// Resize instructs the provider to change the flavor of the server.
Ash Wilson01626a32014-09-17 10:38:07 -0400330// Note that this implies rebuilding it.
331// Unfortunately, one cannot pass rebuild parameters to the resize function.
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700332// When the resize completes, the server will be in RESIZE_VERIFY state.
333// While in this state, you can explore the use of the new server's configuration.
334// If you like it, call ConfirmResize() to commit the resize permanently.
335// Otherwise, call RevertResize() to restore the old configuration.
Ash Wilson01626a32014-09-17 10:38:07 -0400336func Resize(client *gophercloud.ServiceClient, id, flavorRef string) error {
Ash Wilson31f6bde2014-09-25 14:52:12 -0400337 _, err := perigee.Request("POST", actionURL(client, id), perigee.Options{
Jon Perritt30558642014-04-14 17:07:12 -0500338 ReqBody: struct {
339 R map[string]interface{} `json:"resize"`
340 }{
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700341 map[string]interface{}{"flavorRef": flavorRef},
342 },
Ash Wilson01626a32014-09-17 10:38:07 -0400343 MoreHeaders: client.Provider.AuthenticatedHeaders(),
Jon Perritt30558642014-04-14 17:07:12 -0500344 OkCodes: []int{202},
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700345 })
346 return err
347}
348
349// ConfirmResize confirms a previous resize operation on a server.
350// See Resize() for more details.
Ash Wilson01626a32014-09-17 10:38:07 -0400351func ConfirmResize(client *gophercloud.ServiceClient, id string) error {
Ash Wilson31f6bde2014-09-25 14:52:12 -0400352 _, err := perigee.Request("POST", actionURL(client, id), perigee.Options{
Jon Perritt30558642014-04-14 17:07:12 -0500353 ReqBody: map[string]interface{}{"confirmResize": nil},
Ash Wilson01626a32014-09-17 10:38:07 -0400354 MoreHeaders: client.Provider.AuthenticatedHeaders(),
Jon Perritt30558642014-04-14 17:07:12 -0500355 OkCodes: []int{204},
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700356 })
357 return err
358}
359
360// RevertResize cancels a previous resize operation on a server.
361// See Resize() for more details.
Ash Wilson01626a32014-09-17 10:38:07 -0400362func RevertResize(client *gophercloud.ServiceClient, id string) error {
Ash Wilson31f6bde2014-09-25 14:52:12 -0400363 _, err := perigee.Request("POST", actionURL(client, id), perigee.Options{
Jon Perritt30558642014-04-14 17:07:12 -0500364 ReqBody: map[string]interface{}{"revertResize": nil},
Ash Wilson01626a32014-09-17 10:38:07 -0400365 MoreHeaders: client.Provider.AuthenticatedHeaders(),
Jon Perritt30558642014-04-14 17:07:12 -0500366 OkCodes: []int{202},
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700367 })
368 return err
369}