Use clients and pagination for Servers, too.
Whew.
diff --git a/openstack/compute/v2/servers/client.go b/openstack/compute/v2/servers/client.go
deleted file mode 100644
index 8c79f94..0000000
--- a/openstack/compute/v2/servers/client.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package servers
-
-import (
- "fmt"
-
- "github.com/rackspace/gophercloud"
- identity "github.com/rackspace/gophercloud/openstack/identity/v2"
-)
-
-// Client abstracts the connection information needed to make API requests for OpenStack compute endpoints.
-type Client struct {
- endpoint string
- authority identity.AuthResults
- options gophercloud.AuthOptions
- token *identity.Token
-}
-
-// NewClient creates a new Client structure to use when issuing requests to the server.
-func NewClient(e string, a identity.AuthResults, o gophercloud.AuthOptions) *Client {
- return &Client{
- endpoint: e,
- authority: a,
- options: o,
- }
-}
-
-func (c *Client) getListUrl() string {
- return fmt.Sprintf("%s/servers/detail", c.endpoint)
-}
-
-func (c *Client) getCreateUrl() string {
- return fmt.Sprintf("%s/servers", c.endpoint)
-}
-
-func (c *Client) getDeleteUrl(id string) string {
- return fmt.Sprintf("%s/servers/%s", c.endpoint, id)
-}
-
-func (c *Client) getDetailUrl(id string) string {
- return c.getDeleteUrl(id)
-}
-
-func (c *Client) getUpdateUrl(id string) string {
- return c.getDeleteUrl(id)
-}
-
-func (c *Client) getActionUrl(id string) string {
- return fmt.Sprintf("%s/servers/%s/action", c.endpoint, id)
-}
-
-func (c *Client) getListHeaders() (map[string]string, error) {
- t, err := c.getAuthToken()
- if err != nil {
- return map[string]string{}, err
- }
-
- return map[string]string{
- "X-Auth-Token": t,
- }, nil
-}
-
-func (c *Client) getCreateHeaders() (map[string]string, error) {
- return c.getListHeaders()
-}
-
-func (c *Client) getDeleteHeaders() (map[string]string, error) {
- return c.getListHeaders()
-}
-
-func (c *Client) getDetailHeaders() (map[string]string, error) {
- return c.getListHeaders()
-}
-
-func (c *Client) getUpdateHeaders() (map[string]string, error) {
- return c.getListHeaders()
-}
-
-func (c *Client) getActionHeaders() (map[string]string, error) {
- return c.getListHeaders()
-}
-
-func (c *Client) getAuthToken() (string, error) {
- var err error
-
- if c.token == nil {
- c.token, err = identity.GetToken(c.authority)
- if err != nil {
- return "", err
- }
- }
-
- return c.token.ID, err
-}
diff --git a/openstack/compute/v2/servers/requests.go b/openstack/compute/v2/servers/requests.go
index 95818da..e2875e3 100644
--- a/openstack/compute/v2/servers/requests.go
+++ b/openstack/compute/v2/servers/requests.go
@@ -2,123 +2,111 @@
import (
"fmt"
+
"github.com/racker/perigee"
+ "github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/pagination"
)
-// ListResult abstracts the raw results of making a List() request against the
-// API. As OpenStack extensions may freely alter the response bodies of
-// structures returned to the client, you may only safely access the data
-// provided through separate, type-safe accessors or methods.
-type ListResult map[string]interface{}
+// ListResult abstracts the raw results of making a List() request against the API.
+// As OpenStack extensions may freely alter the response bodies of structures returned to the client, you may only safely access the
+// data provided through the ExtractServers call.
+type ListResult struct {
+ pagination.MarkerPageBase
+}
-// ServerResult abstracts a single server description,
-// as returned by the OpenStack provider.
-// As OpenStack extensions may freely alter the response bodies of the
-// structures returned to the client,
-// you may only safely access the data provided through
-// separate, type-safe accessors or methods.
+// IsEmpty returns true if a page contains no Server results.
+func (page ListResult) IsEmpty() (bool, error) {
+ servers, err := ExtractServers(page)
+ if err != nil {
+ return true, err
+ }
+ return len(servers) == 0, nil
+}
+
+// LastMarker returns the ID of the final server on the current page.
+func (page ListResult) LastMarker() (string, error) {
+ servers, err := ExtractServers(page)
+ if err != nil {
+ return "", err
+ }
+ if len(servers) == 0 {
+ return "", nil
+ }
+ return servers[len(servers)-1].ID, nil
+}
+
+// ServerResult abstracts a single server description, as returned by the OpenStack provider.
+// As OpenStack extensions may freely alter the response bodies of the structures returned to the client,
+// you may only safely access the data provided through separate, type-safe accessors or methods.
type ServerResult map[string]interface{}
// List makes a request against the API to list servers accessible to you.
-func List(c *Client) (ListResult, error) {
- var lr ListResult
-
- h, err := c.getListHeaders()
- if err != nil {
- return nil, err
+func List(client *gophercloud.ServiceClient) pagination.Pager {
+ createPage := func(r pagination.LastHTTPResponse) pagination.Page {
+ p := ListResult{pagination.MarkerPageBase{LastHTTPResponse: r}}
+ p.MarkerPageBase.Owner = p
+ return p
}
- err = perigee.Get(c.getListUrl(), perigee.Options{
- Results: &lr,
- MoreHeaders: h,
- })
- return lr, err
+ return pagination.NewPager(client, getListURL(client), createPage)
}
// Create requests a server to be provisioned to the user in the current tenant.
-func Create(c *Client, opts map[string]interface{}) (ServerResult, error) {
+func Create(client *gophercloud.ServiceClient, opts map[string]interface{}) (ServerResult, error) {
var sr ServerResult
-
- h, err := c.getCreateHeaders()
- if err != nil {
- return nil, err
- }
-
- err = perigee.Post(c.getCreateUrl(), perigee.Options{
+ _, err := perigee.Request("POST", getListURL(client), perigee.Options{
Results: &sr,
ReqBody: map[string]interface{}{
"server": opts,
},
- MoreHeaders: h,
+ MoreHeaders: client.Provider.AuthenticatedHeaders(),
OkCodes: []int{202},
})
return sr, err
}
// Delete requests that a server previously provisioned be removed from your account.
-func Delete(c *Client, id string) error {
- h, err := c.getDeleteHeaders()
- if err != nil {
- return err
- }
-
- err = perigee.Delete(c.getDeleteUrl(id), perigee.Options{
- MoreHeaders: h,
+func Delete(client *gophercloud.ServiceClient, id string) error {
+ _, err := perigee.Request("DELETE", getServerURL(client, id), perigee.Options{
+ MoreHeaders: client.Provider.AuthenticatedHeaders(),
OkCodes: []int{204},
})
return err
}
// GetDetail requests details on a single server, by ID.
-func GetDetail(c *Client, id string) (ServerResult, error) {
+func GetDetail(client *gophercloud.ServiceClient, id string) (ServerResult, error) {
var sr ServerResult
-
- h, err := c.getDetailHeaders()
- if err != nil {
- return nil, err
- }
-
- err = perigee.Get(c.getDetailUrl(id), perigee.Options{
+ _, err := perigee.Request("GET", getServerURL(client, id), perigee.Options{
Results: &sr,
- MoreHeaders: h,
+ MoreHeaders: client.Provider.AuthenticatedHeaders(),
})
return sr, err
}
// Update requests that various attributes of the indicated server be changed.
-func Update(c *Client, id string, opts map[string]interface{}) (ServerResult, error) {
+func Update(client *gophercloud.ServiceClient, id string, opts map[string]interface{}) (ServerResult, error) {
var sr ServerResult
-
- h, err := c.getUpdateHeaders()
- if err != nil {
- return nil, err
- }
-
- err = perigee.Put(c.getUpdateUrl(id), perigee.Options{
+ _, err := perigee.Request("PUT", getServerURL(client, id), perigee.Options{
Results: &sr,
ReqBody: map[string]interface{}{
"server": opts,
},
- MoreHeaders: h,
+ MoreHeaders: client.Provider.AuthenticatedHeaders(),
})
return sr, err
}
-// ChangeAdminPassword alters the administrator or root password for a specified
-// server.
-func ChangeAdminPassword(c *Client, id, newPassword string) error {
- h, err := c.getActionHeaders()
- if err != nil {
- return err
- }
-
- err = perigee.Post(c.getActionUrl(id), perigee.Options{
+// ChangeAdminPassword alters the administrator or root password for a specified server.
+func ChangeAdminPassword(client *gophercloud.ServiceClient, id, newPassword string) error {
+ _, err := perigee.Request("POST", getActionURL(client, id), perigee.Options{
ReqBody: struct {
C map[string]string `json:"changePassword"`
}{
map[string]string{"adminPass": newPassword},
},
- MoreHeaders: h,
+ MoreHeaders: client.Provider.AuthenticatedHeaders(),
OkCodes: []int{202},
})
return err
@@ -150,28 +138,29 @@
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 = "SOFT"
- HardReboot = "HARD"
- OSReboot = SoftReboot
- PowerCycle = HardReboot
+ 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.
+// 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). This approach 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 wasking Windows to restart the
-// machine.
-func Reboot(c *Client, id, how string) error {
+// 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",
@@ -180,36 +169,28 @@
}
}
- h, err := c.getActionHeaders()
- if err != nil {
- return err
- }
-
- err = perigee.Post(c.getActionUrl(id), perigee.Options{
+ _, err := perigee.Request("POST", getActionURL(client, id), perigee.Options{
ReqBody: struct {
C map[string]string `json:"reboot"`
}{
- map[string]string{"type": how},
+ map[string]string{"type": string(how)},
},
- MoreHeaders: h,
+ 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.
+// 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(c *Client, id, name, password, imageRef string, additional map[string]interface{}) (ServerResult, error) {
+// 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{}) (ServerResult, error) {
var sr ServerResult
if id == "" {
@@ -252,43 +233,34 @@
additional["imageRef"] = imageRef
additional["adminPass"] = password
- h, err := c.getActionHeaders()
- if err != nil {
- return sr, err
- }
-
- err = perigee.Post(c.getActionUrl(id), perigee.Options{
+ _, err := perigee.Request("POST", getActionURL(client, id), perigee.Options{
ReqBody: struct {
R map[string]interface{} `json:"rebuild"`
}{
additional,
},
Results: &sr,
- MoreHeaders: h,
+ MoreHeaders: client.Provider.AuthenticatedHeaders(),
OkCodes: []int{202},
})
return sr, err
}
// 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.
+// 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(c *Client, id, flavorRef string) error {
- h, err := c.getActionHeaders()
- if err != nil {
- return err
- }
-
- err = perigee.Post(c.getActionUrl(id), perigee.Options{
+func Resize(client *gophercloud.ServiceClient, id, flavorRef string) error {
+ _, err := perigee.Request("POST", getActionURL(client, id), perigee.Options{
ReqBody: struct {
R map[string]interface{} `json:"resize"`
}{
map[string]interface{}{"flavorRef": flavorRef},
},
- MoreHeaders: h,
+ MoreHeaders: client.Provider.AuthenticatedHeaders(),
OkCodes: []int{202},
})
return err
@@ -296,15 +268,10 @@
// ConfirmResize confirms a previous resize operation on a server.
// See Resize() for more details.
-func ConfirmResize(c *Client, id string) error {
- h, err := c.getActionHeaders()
- if err != nil {
- return err
- }
-
- err = perigee.Post(c.getActionUrl(id), perigee.Options{
+func ConfirmResize(client *gophercloud.ServiceClient, id string) error {
+ _, err := perigee.Request("POST", getActionURL(client, id), perigee.Options{
ReqBody: map[string]interface{}{"confirmResize": nil},
- MoreHeaders: h,
+ MoreHeaders: client.Provider.AuthenticatedHeaders(),
OkCodes: []int{204},
})
return err
@@ -312,15 +279,10 @@
// RevertResize cancels a previous resize operation on a server.
// See Resize() for more details.
-func RevertResize(c *Client, id string) error {
- h, err := c.getActionHeaders()
- if err != nil {
- return err
- }
-
- err = perigee.Post(c.getActionUrl(id), perigee.Options{
+func RevertResize(client *gophercloud.ServiceClient, id string) error {
+ _, err := perigee.Request("POST", getActionURL(client, id), perigee.Options{
ReqBody: map[string]interface{}{"revertResize": nil},
- MoreHeaders: h,
+ MoreHeaders: client.Provider.AuthenticatedHeaders(),
OkCodes: []int{202},
})
return err
diff --git a/openstack/compute/v2/servers/servers.go b/openstack/compute/v2/servers/servers.go
index 28d66d0..865c7d1 100644
--- a/openstack/compute/v2/servers/servers.go
+++ b/openstack/compute/v2/servers/servers.go
@@ -1,89 +1,80 @@
package servers
import (
- "fmt"
+ "errors"
+
"github.com/mitchellh/mapstructure"
+ "github.com/rackspace/gophercloud/pagination"
)
-// ErrNotImplemented indicates a failure to discover a feature of the response from the API.
-// E.g., a missing server field, a missing extension, etc.
-var ErrNotImplemented = fmt.Errorf("Compute Servers feature not implemented.")
+// ErrCannotInterpret is returned by an Extract call if the response body doesn't have the expected structure.
+var ErrCannotInterpet = errors.New("Unable to interpret a response body.")
// Server exposes only the standard OpenStack fields corresponding to a given server on the user's account.
-//
-// Id uniquely identifies this server amongst all other servers, including those not accessible to the current tenant.
-//
-// TenantId identifies the tenant owning this server resource.
-//
-// UserId uniquely identifies the user account owning the tenant.
-//
-// Name contains the human-readable name for the server.
-//
-// Updated and Created contain ISO-8601 timestamps of when the state of the server last changed, and when it was created.
-//
-// Status contains the current operational status of the server, such as IN_PROGRESS or ACTIVE.
-//
-// Progress ranges from 0..100. A request made against the server completes only once Progress reaches 100.
-//
-// AccessIPv4 and AccessIPv6 contain the IP addresses of the server, suitable for remote access for administration.
-//
-// Image refers to a JSON object, which itself indicates the OS image used to deploy the server.
-//
-// Flavor refers to a JSON object, which itself indicates the hardware configuration of the deployed server.
-//
-// Addresses includes a list of all IP addresses assigned to the server, keyed by pool.
-//
-// Metadata includes a list of all user-specified key-value pairs attached to the server.
-//
-// Links includes HTTP references to the itself, useful for passing along to other APIs that might want a server reference.
-//
-// AdminPass will generally be empty (""). However, it will contain the administrative password chosen when provisioning a new server without a set AdminPass setting in the first place.
-// Note that this is the ONLY time this field will be valid.
type Server struct {
- Id string
- TenantId string `mapstructure:tenant_id`
- UserId string `mapstructure:user_id`
- Name string
- Updated string
- Created string
- HostId string
- Status string
- Progress int
+ // ID uniquely identifies this server amongst all other servers, including those not accessible to the current tenant.
+ ID string
+
+ // TenantID identifies the tenant owning this server resource.
+ TenantID string `mapstructure:"tenant_id"`
+
+ // UserID uniquely identifies the user account owning the tenant.
+ UserID string `mapstructure:"user_id"`
+
+ // Name contains the human-readable name for the server.
+ Name string
+
+ // Updated and Created contain ISO-8601 timestamps of when the state of the server last changed, and when it was created.
+ Updated string
+ Created string
+
+ HostID string
+
+ // Status contains the current operational status of the server, such as IN_PROGRESS or ACTIVE.
+ Status string
+
+ // Progress ranges from 0..100.
+ // A request made against the server completes only once Progress reaches 100.
+ Progress int
+
+ // AccessIPv4 and AccessIPv6 contain the IP addresses of the server, suitable for remote access for administration.
AccessIPv4 string
AccessIPv6 string
- Image map[string]interface{}
- Flavor map[string]interface{}
- Addresses map[string]interface{}
- Metadata map[string]interface{}
- Links []interface{}
- AdminPass string `mapstructure:adminPass`
+
+ // Image refers to a JSON object, which itself indicates the OS image used to deploy the server.
+ Image map[string]interface{}
+
+ // Flavor refers to a JSON object, which itself indicates the hardware configuration of the deployed server.
+ Flavor map[string]interface{}
+
+ // Addresses includes a list of all IP addresses assigned to the server, keyed by pool.
+
+ Addresses map[string]interface{}
+
+ // Metadata includes a list of all user-specified key-value pairs attached to the server.
+ Metadata map[string]interface{}
+
+ // Links includes HTTP references to the itself, useful for passing along to other APIs that might want a server reference.
+ Links []interface{}
+
+ // AdminPass will generally be empty (""). However, it will contain the administrative password chosen when provisioning a new server without a set AdminPass setting in the first place.
+ // Note that this is the ONLY time this field will be valid.
+ AdminPass string `mapstructure:"adminPass"`
}
-// GetServers interprets the result of a List() call, producing a slice of Server entities.
-func GetServers(lr ListResult) ([]Server, error) {
- sa, ok := lr["servers"]
- if !ok {
- return nil, ErrNotImplemented
- }
- serversArray := sa.([]interface{})
-
- servers := make([]Server, len(serversArray))
- for i, so := range serversArray {
- serverObj := so.(map[string]interface{})
- err := mapstructure.Decode(serverObj, &servers[i])
- if err != nil {
- return servers, err
- }
- }
-
- return servers, nil
+// ExtractServers interprets the results of a single page from a List() call, producing a slice of Server entities.
+func ExtractServers(page pagination.Page) ([]Server, error) {
+ casted := page.(ListResult).Body
+ var servers []Server
+ err := mapstructure.Decode(servers, casted)
+ return servers, err
}
-// GetServer interprets the result of a call expected to return data on a single server.
-func GetServer(sr ServerResult) (*Server, error) {
+// ExtractServer interprets the result of a call expected to return data on a single server.
+func ExtractServer(sr ServerResult) (*Server, error) {
so, ok := sr["server"]
if !ok {
- return nil, ErrNotImplemented
+ return nil, ErrCannotInterpet
}
serverObj := so.(map[string]interface{})
s := new(Server)
diff --git a/openstack/compute/v2/servers/urls.go b/openstack/compute/v2/servers/urls.go
new file mode 100644
index 0000000..3440de1
--- /dev/null
+++ b/openstack/compute/v2/servers/urls.go
@@ -0,0 +1,19 @@
+package servers
+
+import "github.com/rackspace/gophercloud"
+
+func getListURL(client *gophercloud.ServiceClient) string {
+ return client.ServiceURL("servers", "detail")
+}
+
+func getCreateURL(client *gophercloud.ServiceClient) string {
+ return client.ServiceURL("servers")
+}
+
+func getServerURL(client *gophercloud.ServiceClient, id string) string {
+ return client.ServiceURL("servers", id)
+}
+
+func getActionURL(client *gophercloud.ServiceClient, id string) string {
+ return client.ServiceURL("servers", id, "action")
+}