blob: 9223772c4f29da4706513c04621b2e52d540bcb2 [file] [log] [blame]
package servers
import (
"encoding/base64"
"fmt"
"github.com/racker/perigee"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
// List makes a request against the API to list servers accessible to you.
func List(client *gophercloud.ServiceClient) pagination.Pager {
createPage := func(r pagination.LastHTTPResponse) pagination.Page {
return ServerPage{pagination.LinkedPageBase{LastHTTPResponse: r}}
}
return pagination.NewPager(client, detailURL(client), createPage)
}
// CreateOptsLike describes struct types that can be accepted by the Create call.
// The CreateOpts struct in this package does.
type CreateOptsLike interface {
ToServerCreateReqData() map[string]interface{}
}
// Network is used within CreateOpts to control a new server's network attachments.
type Network struct {
// UUID of a nova-network to attach to the newly provisioned server.
// Required unless Port is provided.
UUID string
// Port of a neutron network to attach to the newly provisioned server.
// Required unless UUID is provided.
Port string
// FixedIP [optional] specifies a fixed IPv4 address to be used on this network.
FixedIP string
}
// CreateOpts specifies server creation parameters.
type CreateOpts struct {
// Name [required] is the name to assign to the newly launched server.
Name string
// ImageRef [required] is the ID or full URL to the image that contains the server's OS and initial state.
// Optional if using the boot-from-volume extension.
ImageRef string
// FlavorRef [required] is the ID or full URL to the flavor that describes the server's specs.
FlavorRef string
// SecurityGroups [optional] lists the names of the security groups to which this server should belong.
SecurityGroups []string
// UserData [optional] contains configuration information or scripts to use upon launch.
// Create will base64-encode it for you.
UserData []byte
// AvailabilityZone [optional] in which to launch the server.
AvailabilityZone string
// Networks [optional] dictates how this server will be attached to available networks.
// By default, the server will be attached to all isolated networks for the tenant.
Networks []Network
// Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the server.
Metadata map[string]string
// Personality [optional] includes the path and contents of a file to inject into the server at launch.
// The maximum size of the file is 255 bytes (decoded).
Personality []byte
// ConfigDrive [optional] enables metadata injection through a configuration drive.
ConfigDrive bool
}
// ToServerCreateReqData assembles a request body based on the contents of a CreateOpts.
func (opts CreateOpts) ToServerCreateReqData() map[string]interface{} {
server := make(map[string]interface{})
server["name"] = opts.Name
server["imageRef"] = opts.ImageRef
server["flavorRef"] = opts.FlavorRef
if opts.UserData != nil {
encoded := base64.StdEncoding.EncodeToString(opts.UserData)
server["user_data"] = &encoded
}
if opts.Personality != nil {
encoded := base64.StdEncoding.EncodeToString(opts.Personality)
server["personality"] = &encoded
}
if opts.ConfigDrive {
server["config_drive"] = "true"
}
if opts.AvailabilityZone != "" {
server["availability_zone"] = opts.AvailabilityZone
}
if opts.Metadata != nil {
server["metadata"] = opts.Metadata
}
if len(opts.SecurityGroups) > 0 {
securityGroups := make([]map[string]interface{}, len(opts.SecurityGroups))
for i, groupName := range opts.SecurityGroups {
securityGroups[i] = map[string]interface{}{"name": groupName}
}
}
if len(opts.Networks) > 0 {
networks := make([]map[string]interface{}, len(opts.Networks))
for i, net := range opts.Networks {
networks[i] = make(map[string]interface{})
if net.UUID != "" {
networks[i]["uuid"] = net.UUID
}
if net.Port != "" {
networks[i]["port"] = net.Port
}
if net.FixedIP != "" {
networks[i]["fixed_ip"] = net.FixedIP
}
}
}
return map[string]interface{}{"server": server}
}
// Create requests a server to be provisioned to the user in the current tenant.
func Create(client *gophercloud.ServiceClient, opts CreateOptsLike) CreateResult {
var result CreateResult
_, result.Err = perigee.Request("POST", listURL(client), perigee.Options{
Results: &result.Resp,
ReqBody: opts.ToServerCreateReqData(),
MoreHeaders: client.Provider.AuthenticatedHeaders(),
OkCodes: []int{202},
})
return result
}
// Delete requests that a server previously provisioned be removed from your account.
func Delete(client *gophercloud.ServiceClient, id string) error {
_, err := perigee.Request("DELETE", serverURL(client, id), perigee.Options{
MoreHeaders: client.Provider.AuthenticatedHeaders(),
OkCodes: []int{204},
})
return err
}
// Get requests details on a single server, by ID.
func Get(client *gophercloud.ServiceClient, id string) GetResult {
var result GetResult
_, result.Err = perigee.Request("GET", serverURL(client, id), perigee.Options{
Results: &result.Resp,
MoreHeaders: client.Provider.AuthenticatedHeaders(),
})
return result
}
// UpdateOptsLike allows extentions to add additional attributes to the Update request.
type UpdateOptsLike interface {
ToServerUpdateReqBody() map[string]interface{}
}
// UpdateOpts specifies the base attributes that may be updated on an existing server.
type UpdateOpts struct {
// Name [optional] changes the displayed name of the server.
// The server host name will *not* change.
// Server names are not constrained to be unique, even within the same tenant.
Name string
// AccessIPv4 [optional] provides a new IPv4 address for the instance.
AccessIPv4 string
// AccessIPv6 [optional] provides a new IPv6 address for the instance.
AccessIPv6 string
}
// ToServerUpdateReqBody formats an UpdateOpts structure into a request body.
func (opts UpdateOpts) ToServerUpdateReqBody() map[string]interface{} {
server := make(map[string]string)
if opts.Name != "" {
server["name"] = opts.Name
}
if opts.AccessIPv4 != "" {
server["accessIPv4"] = opts.AccessIPv4
}
if opts.AccessIPv6 != "" {
server["accessIPv6"] = opts.AccessIPv6
}
return map[string]interface{}{"server": server}
}
// Update requests that various attributes of the indicated server be changed.
func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsLike) UpdateResult {
var result UpdateResult
_, result.Err = perigee.Request("PUT", serverURL(client, id), perigee.Options{
Results: &result.Resp,
ReqBody: opts.ToServerUpdateReqBody(),
MoreHeaders: client.Provider.AuthenticatedHeaders(),
})
return result
}
// ChangeAdminPassword alters the administrator or root password for a specified server.
func ChangeAdminPassword(client *gophercloud.ServiceClient, id, newPassword string) error {
var req struct {
ChangePassword struct {
AdminPass string `json:"adminPass"`
} `json:"changePassword"`
}
req.ChangePassword.AdminPass = newPassword
_, err := perigee.Request("POST", actionURL(client, id), perigee.Options{
ReqBody: req,
MoreHeaders: client.Provider.AuthenticatedHeaders(),
OkCodes: []int{202},
})
return err
}
// ErrArgument errors occur when an argument supplied to a package function
// fails to fall within acceptable values. For example, the Reboot() function
// expects the "how" parameter to be one of HardReboot or SoftReboot. These
// constants are (currently) strings, leading someone to wonder if they can pass
// other string values instead, perhaps in an effort to break the API of their
// provider. Reboot() returns this error in this situation.
//
// Function identifies which function was called/which function is generating
// the error.
// Argument identifies which formal argument was responsible for producing the
// error.
// Value provides the value as it was passed into the function.
type ErrArgument struct {
Function, Argument string
Value interface{}
}
// Error yields a useful diagnostic for debugging purposes.
func (e *ErrArgument) Error() string {
return fmt.Sprintf("Bad argument in call to %s, formal parameter %s, value %#v", e.Function, e.Argument, e.Value)
}
func (e *ErrArgument) String() string {
return e.Error()
}
// RebootMethod describes the mechanisms by which a server reboot can be requested.
type RebootMethod string
// These constants determine how a server should be rebooted.
// See the Reboot() function for further details.
const (
SoftReboot RebootMethod = "SOFT"
HardReboot RebootMethod = "HARD"
OSReboot = SoftReboot
PowerCycle = HardReboot
)
// Reboot requests that a given server reboot.
// Two methods exist for rebooting a server:
//
// HardReboot (aka PowerCycle) restarts the server instance by physically cutting power to the machine, or if a VM,
// terminating it at the hypervisor level.
// It's done. Caput. Full stop.
// Then, after a brief while, power is restored or the VM instance restarted.
//
// SoftReboot (aka OSReboot) simply tells the OS to restart under its own procedures.
// E.g., in Linux, asking it to enter runlevel 6, or executing "sudo shutdown -r now", or by asking Windows to restart the machine.
func Reboot(client *gophercloud.ServiceClient, id string, how RebootMethod) error {
if (how != SoftReboot) && (how != HardReboot) {
return &ErrArgument{
Function: "Reboot",
Argument: "how",
Value: how,
}
}
_, err := perigee.Request("POST", actionURL(client, id), perigee.Options{
ReqBody: struct {
C map[string]string `json:"reboot"`
}{
map[string]string{"type": string(how)},
},
MoreHeaders: client.Provider.AuthenticatedHeaders(),
OkCodes: []int{202},
})
return err
}
// Rebuild requests that the Openstack provider reprovision the server.
// The rebuild will need to know the server's name and new image reference or ID.
// In addition, and unlike building a server with Create(), you must provide an administrator password.
//
// Additional options may be specified with the additional map.
// This function treats a nil map the same as an empty map.
//
// Rebuild returns a server result as though you had called GetDetail() on the server's ID.
// The information, however, refers to the new server, not the old.
func Rebuild(client *gophercloud.ServiceClient, id, name, password, imageRef string, additional map[string]interface{}) RebuildResult {
var result RebuildResult
if id == "" {
result.Err = &ErrArgument{
Function: "Rebuild",
Argument: "id",
Value: "",
}
return result
}
if name == "" {
result.Err = &ErrArgument{
Function: "Rebuild",
Argument: "name",
Value: "",
}
return result
}
if password == "" {
result.Err = &ErrArgument{
Function: "Rebuild",
Argument: "password",
Value: "",
}
return result
}
if imageRef == "" {
result.Err = &ErrArgument{
Function: "Rebuild",
Argument: "imageRef",
Value: "",
}
return result
}
if additional == nil {
additional = make(map[string]interface{}, 0)
}
additional["name"] = name
additional["imageRef"] = imageRef
additional["adminPass"] = password
_, result.Err = perigee.Request("POST", actionURL(client, id), perigee.Options{
ReqBody: struct {
R map[string]interface{} `json:"rebuild"`
}{
additional,
},
Results: &result.Resp,
MoreHeaders: client.Provider.AuthenticatedHeaders(),
OkCodes: []int{202},
})
return result
}
// Resize instructs the provider to change the flavor of the server.
// Note that this implies rebuilding it.
// Unfortunately, one cannot pass rebuild parameters to the resize function.
// When the resize completes, the server will be in RESIZE_VERIFY state.
// While in this state, you can explore the use of the new server's configuration.
// If you like it, call ConfirmResize() to commit the resize permanently.
// Otherwise, call RevertResize() to restore the old configuration.
func Resize(client *gophercloud.ServiceClient, id, flavorRef string) error {
_, err := perigee.Request("POST", actionURL(client, id), perigee.Options{
ReqBody: struct {
R map[string]interface{} `json:"resize"`
}{
map[string]interface{}{"flavorRef": flavorRef},
},
MoreHeaders: client.Provider.AuthenticatedHeaders(),
OkCodes: []int{202},
})
return err
}
// ConfirmResize confirms a previous resize operation on a server.
// See Resize() for more details.
func ConfirmResize(client *gophercloud.ServiceClient, id string) error {
_, err := perigee.Request("POST", actionURL(client, id), perigee.Options{
ReqBody: map[string]interface{}{"confirmResize": nil},
MoreHeaders: client.Provider.AuthenticatedHeaders(),
OkCodes: []int{204},
})
return err
}
// RevertResize cancels a previous resize operation on a server.
// See Resize() for more details.
func RevertResize(client *gophercloud.ServiceClient, id string) error {
_, err := perigee.Request("POST", actionURL(client, id), perigee.Options{
ReqBody: map[string]interface{}{"revertResize": nil},
MoreHeaders: client.Provider.AuthenticatedHeaders(),
OkCodes: []int{202},
})
return err
}