more error types for compute v2
diff --git a/openstack/compute/v2/extensions/bootfromvolume/requests.go b/openstack/compute/v2/extensions/bootfromvolume/requests.go
index 6c063be..7171afa 100644
--- a/openstack/compute/v2/extensions/bootfromvolume/requests.go
+++ b/openstack/compute/v2/extensions/bootfromvolume/requests.go
@@ -1,7 +1,7 @@
package bootfromvolume
import (
- "errors"
+ "fmt"
"strconv"
"github.com/gophercloud/gophercloud"
@@ -62,7 +62,10 @@
}
if len(opts.BlockDevice) == 0 {
- return nil, errors.New("Required fields UUID and SourceType not set.")
+ err := gophercloud.ErrMissingInput{}
+ err.Function = "bootfromvolume.ToServerCreateMap"
+ err.Argument = "bootfromvolume.CreateOptsExt.BlockDevice"
+ return nil, err
}
serverMap := base["server"].(map[string]interface{})
@@ -71,7 +74,10 @@
for i, bd := range opts.BlockDevice {
if string(bd.SourceType) == "" {
- return nil, errors.New("SourceType must be one of: volume, image, snapshot.")
+ err := gophercloud.ErrMissingInput{}
+ err.Function = "bootfromvolume.ToServerCreateMap"
+ err.Argument = fmt.Sprintf("bootfromvolume.CreateOptsExt.BlockDevice[%d].SourceType", i)
+ return nil, err
}
blockDevice[i] = make(map[string]interface{})
diff --git a/openstack/compute/v2/extensions/defsecrules/requests.go b/openstack/compute/v2/extensions/defsecrules/requests.go
index fc3e3a8..5e2d686 100644
--- a/openstack/compute/v2/extensions/defsecrules/requests.go
+++ b/openstack/compute/v2/extensions/defsecrules/requests.go
@@ -1,8 +1,6 @@
package defsecrules
import (
- "errors"
-
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
@@ -40,21 +38,33 @@
// ToRuleCreateMap builds the create rule options into a serializable format.
func (opts CreateOpts) ToRuleCreateMap() (map[string]interface{}, error) {
- rule := make(map[string]interface{})
-
if opts.FromPort == 0 {
- return rule, errors.New("A FromPort must be set")
+ err := gophercloud.ErrMissingInput{}
+ err.Function = "defsecrules.ToRuleCreateMap"
+ err.Argument = "defsecrules.CreateOpts.FromPort"
+ return nil, err
}
if opts.ToPort == 0 {
- return rule, errors.New("A ToPort must be set")
+ err := gophercloud.ErrMissingInput{}
+ err.Function = "defsecrules.ToRuleCreateMap"
+ err.Argument = "defsecrules.CreateOpts.ToPort"
+ return nil, err
}
if opts.IPProtocol == "" {
- return rule, errors.New("A IPProtocol must be set")
+ err := gophercloud.ErrMissingInput{}
+ err.Function = "defsecrules.ToRuleCreateMap"
+ err.Argument = "defsecrules.CreateOpts.IPProtocol"
+ return nil, err
}
if opts.CIDR == "" {
- return rule, errors.New("A CIDR must be set")
+ err := gophercloud.ErrMissingInput{}
+ err.Function = "defsecrules.ToRuleCreateMap"
+ err.Argument = "defsecrules.CreateOpts.CIDR"
+ return nil, err
}
+ rule := make(map[string]interface{})
+
rule["from_port"] = opts.FromPort
rule["to_port"] = opts.ToPort
rule["ip_protocol"] = opts.IPProtocol
diff --git a/openstack/compute/v2/extensions/diskconfig/requests.go b/openstack/compute/v2/extensions/diskconfig/requests.go
index 3fd9c9e..19798d7 100644
--- a/openstack/compute/v2/extensions/diskconfig/requests.go
+++ b/openstack/compute/v2/extensions/diskconfig/requests.go
@@ -1,8 +1,7 @@
package diskconfig
import (
- "errors"
-
+ "github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
)
@@ -23,19 +22,6 @@
Manual DiskConfig = "MANUAL"
)
-// ErrInvalidDiskConfig is returned if an invalid string is specified for a DiskConfig option.
-var ErrInvalidDiskConfig = errors.New("DiskConfig must be either diskconfig.Auto or diskconfig.Manual.")
-
-// Validate ensures that a DiskConfig contains an appropriate value.
-func (config DiskConfig) validate() error {
- switch config {
- case Auto, Manual:
- return nil
- default:
- return ErrInvalidDiskConfig
- }
-}
-
// CreateOptsExt adds a DiskConfig option to the base CreateOpts.
type CreateOptsExt struct {
servers.CreateOptsBuilder
@@ -71,8 +57,11 @@
// ToServerRebuildMap adds the diskconfig option to the base server rebuild options.
func (opts RebuildOptsExt) ToServerRebuildMap() (map[string]interface{}, error) {
- err := opts.DiskConfig.validate()
- if err != nil {
+ if opts.DiskConfig != Auto && opts.DiskConfig != Manual {
+ err := gophercloud.ErrInvalidInput{}
+ err.Function = "diskconfig.ToServerRebuildMap"
+ err.Argument = "diskconfig.RebuildOptsExt.DiskConfig"
+ err.Info = "Must be either diskconfig.Auto or diskconfig.Manual"
return nil, err
}
@@ -97,8 +86,11 @@
// ToServerResizeMap adds the diskconfig option to the base server creation options.
func (opts ResizeOptsExt) ToServerResizeMap() (map[string]interface{}, error) {
- err := opts.DiskConfig.validate()
- if err != nil {
+ if opts.DiskConfig != Auto && opts.DiskConfig != Manual {
+ err := gophercloud.ErrInvalidInput{}
+ err.Function = "diskconfig.ToServerResizeMap"
+ err.Argument = "diskconfig.ResizeOptsExt.DiskConfig"
+ err.Info = "Must be either diskconfig.Auto or diskconfig.Manual"
return nil, err
}
diff --git a/openstack/compute/v2/extensions/floatingips/requests.go b/openstack/compute/v2/extensions/floatingips/requests.go
index d99033e..00b2520 100644
--- a/openstack/compute/v2/extensions/floatingips/requests.go
+++ b/openstack/compute/v2/extensions/floatingips/requests.go
@@ -1,8 +1,6 @@
package floatingips
import (
- "errors"
-
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
@@ -41,7 +39,10 @@
// ToFloatingIPCreateMap constructs a request body from CreateOpts.
func (opts CreateOpts) ToFloatingIPCreateMap() (map[string]interface{}, error) {
if opts.Pool == "" {
- return nil, errors.New("Missing field required for floating IP creation: Pool")
+ err := gophercloud.ErrMissingInput{}
+ err.Function = "floatingips.ToFloatingIPCreateMap"
+ err.Argument = "floatingips.CreateOpts.Pool"
+ return nil, err
}
return map[string]interface{}{"pool": opts.Pool}, nil
@@ -50,11 +51,17 @@
// ToAssociateMap constructs a request body from AssociateOpts.
func (opts AssociateOpts) ToAssociateMap() (map[string]interface{}, error) {
if opts.ServerID == "" {
- return nil, errors.New("Required field missing for floating IP association: ServerID")
+ err := gophercloud.ErrMissingInput{}
+ err.Function = "floatingips.ToAssociateMap"
+ err.Argument = "floatingips.AssociateOpts.ServerID"
+ return nil, err
}
if opts.FloatingIP == "" {
- return nil, errors.New("Required field missing for floating IP association: FloatingIP")
+ err := gophercloud.ErrMissingInput{}
+ err.Function = "floatingips.ToAssociateMap"
+ err.Argument = "floatingips.AssociateOpts.FloatingIP"
+ return nil, err
}
associateInfo := map[string]interface{}{
diff --git a/openstack/compute/v2/extensions/keypairs/requests.go b/openstack/compute/v2/extensions/keypairs/requests.go
index af05a8e..b9b32ee 100644
--- a/openstack/compute/v2/extensions/keypairs/requests.go
+++ b/openstack/compute/v2/extensions/keypairs/requests.go
@@ -1,8 +1,6 @@
package keypairs
import (
- "errors"
-
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"github.com/gophercloud/gophercloud/pagination"
@@ -58,7 +56,10 @@
// ToKeyPairCreateMap constructs a request body from CreateOpts.
func (opts CreateOpts) ToKeyPairCreateMap() (map[string]interface{}, error) {
if opts.Name == "" {
- return nil, errors.New("Missing field required for keypair creation: Name")
+ err := gophercloud.ErrMissingInput{}
+ err.Function = "keypairs.ToKeyPairCreateMap"
+ err.Argument = "keypairs.CreateOpts.Name"
+ return nil, err
}
keypair := make(map[string]interface{})
diff --git a/openstack/compute/v2/extensions/schedulerhints/requests.go b/openstack/compute/v2/extensions/schedulerhints/requests.go
index d9aa733..5713e72 100644
--- a/openstack/compute/v2/extensions/schedulerhints/requests.go
+++ b/openstack/compute/v2/extensions/schedulerhints/requests.go
@@ -1,11 +1,11 @@
package schedulerhints
import (
- "fmt"
"net"
"regexp"
"strings"
+ "github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
)
@@ -47,7 +47,12 @@
if opts.Group != "" {
if !uuidRegex.MatchString(opts.Group) {
- return nil, fmt.Errorf("Group must be a UUID")
+ err := gophercloud.ErrInvalidInput{}
+ err.Function = "schedulerhints.ToServerSchedulerHintsMap"
+ err.Argument = "schedulerhints.SchedulerHints.Group"
+ err.Value = opts.Group
+ err.Info = "Group must be a UUID"
+ return nil, err
}
sh["group"] = opts.Group
}
@@ -55,7 +60,12 @@
if len(opts.DifferentHost) > 0 {
for _, diffHost := range opts.DifferentHost {
if !uuidRegex.MatchString(diffHost) {
- return nil, fmt.Errorf("The hosts in DifferentHost must be in UUID format.")
+ err := gophercloud.ErrInvalidInput{}
+ err.Function = "schedulerhints.ToServerSchedulerHintsMap"
+ err.Argument = "schedulerhints.SchedulerHints.DifferentHost"
+ err.Value = opts.DifferentHost
+ err.Info = "The hosts must be in UUID format."
+ return nil, err
}
}
sh["different_host"] = opts.DifferentHost
@@ -64,7 +74,12 @@
if len(opts.SameHost) > 0 {
for _, sameHost := range opts.SameHost {
if !uuidRegex.MatchString(sameHost) {
- return nil, fmt.Errorf("The hosts in SameHost must be in UUID format.")
+ err := gophercloud.ErrInvalidInput{}
+ err.Function = "schedulerhints.ToServerSchedulerHintsMap"
+ err.Argument = "schedulerhints.SchedulerHints.SameHost"
+ err.Value = opts.SameHost
+ err.Info = "The hosts must be in UUID format."
+ return nil, err
}
}
sh["same_host"] = opts.SameHost
@@ -83,7 +98,12 @@
*/
if len(opts.Query) > 0 {
if len(opts.Query) < 3 {
- return nil, fmt.Errorf("Query must be a conditional statement in the format of [op,variable,value]")
+ err := gophercloud.ErrInvalidInput{}
+ err.Function = "schedulerhints.ToServerSchedulerHintsMap"
+ err.Argument = "schedulerhints.SchedulerHints.Query"
+ err.Value = opts.Query
+ err.Info = "Must be a conditional statement in the format of [op,variable,value]"
+ return nil, err
}
sh["query"] = opts.Query
}
@@ -94,7 +114,12 @@
if opts.BuildNearHostIP != "" {
if _, _, err := net.ParseCIDR(opts.BuildNearHostIP); err != nil {
- return nil, fmt.Errorf("BuildNearHostIP must be a valid subnet in the form 192.168.1.1/24")
+ err := gophercloud.ErrInvalidInput{}
+ err.Function = "schedulerhints.ToServerSchedulerHintsMap"
+ err.Argument = "schedulerhints.SchedulerHints.BuildNearHostIP"
+ err.Value = opts.BuildNearHostIP
+ err.Info = "Must be a valid subnet in the form 192.168.1.1/24"
+ return nil, err
}
ipParts := strings.Split(opts.BuildNearHostIP, "/")
sh["build_near_host_ip"] = ipParts[0]
diff --git a/openstack/compute/v2/extensions/secgroups/requests.go b/openstack/compute/v2/extensions/secgroups/requests.go
index 758ba09..a0bb025 100644
--- a/openstack/compute/v2/extensions/secgroups/requests.go
+++ b/openstack/compute/v2/extensions/secgroups/requests.go
@@ -1,8 +1,6 @@
package secgroups
import (
- "errors"
-
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
@@ -46,20 +44,21 @@
ToSecGroupCreateMap() (map[string]interface{}, error)
}
-var (
- errName = errors.New("Name is a required field")
- errDesc = errors.New("Description is a required field")
-)
-
// ToSecGroupCreateMap builds the create options into a serializable format.
func (opts CreateOpts) ToSecGroupCreateMap() (map[string]interface{}, error) {
sg := make(map[string]interface{})
if opts.Name == "" {
- return sg, errName
+ err := gophercloud.ErrMissingInput{}
+ err.Function = "secgroups.ToSecGroupCreateMap"
+ err.Argument = "secgroups.CreateOpts.Name"
+ return nil, err
}
if opts.Description == "" {
- return sg, errDesc
+ err := gophercloud.ErrMissingInput{}
+ err.Function = "secgroups.ToSecGroupCreateMap"
+ err.Argument = "secgroups.CreateOpts.Description"
+ return nil, err
}
sg["name"] = opts.Name
@@ -98,10 +97,16 @@
sg := make(map[string]interface{})
if opts.Name == "" {
- return sg, errName
+ err := gophercloud.ErrMissingInput{}
+ err.Function = "secgroups.ToSecGroupUpdateMap"
+ err.Argument = "secgroups.UpdateOpts.Name"
+ return nil, err
}
if opts.Description == "" {
- return sg, errDesc
+ err := gophercloud.ErrMissingInput{}
+ err.Function = "secgroups.ToSecGroupUpdateMap"
+ err.Argument = "secgroups.UpdateOpts.Description"
+ return nil, err
}
sg["name"] = opts.Name
@@ -176,24 +181,39 @@
// ToRuleCreateMap builds the create rule options into a serializable format.
func (opts CreateRuleOpts) ToRuleCreateMap() (map[string]interface{}, error) {
- rule := make(map[string]interface{})
-
if opts.ParentGroupID == "" {
- return rule, errors.New("A ParentGroupID must be set")
+ err := gophercloud.ErrMissingInput{}
+ err.Function = "secgroups.ToRuleCreateMap"
+ err.Argument = "secgroups.CreateRuleOpts.ParentGroupID"
+ return nil, err
}
if opts.FromPort == 0 {
- return rule, errors.New("A FromPort must be set")
+ err := gophercloud.ErrMissingInput{}
+ err.Function = "secgroups.ToRuleCreateMap"
+ err.Argument = "secgroups.CreateRuleOpts.FromPort"
+ return nil, err
}
if opts.ToPort == 0 {
- return rule, errors.New("A ToPort must be set")
+ err := gophercloud.ErrMissingInput{}
+ err.Function = "secgroups.ToRuleCreateMap"
+ err.Argument = "secgroups.CreateRuleOpts.ToPort"
+ return nil, err
}
if opts.IPProtocol == "" {
- return rule, errors.New("A IPProtocol must be set")
+ err := gophercloud.ErrMissingInput{}
+ err.Function = "secgroups.ToRuleCreateMap"
+ err.Argument = "secgroups.CreateRuleOpts.IPProtocol"
+ return nil, err
}
if opts.CIDR == "" && opts.FromGroupID == "" {
- return rule, errors.New("A CIDR or FromGroupID must be set")
+ err := gophercloud.ErrMissingInput{}
+ err.Function = "secgroups.ToRuleCreateMap"
+ err.Argument = "secgroups.CreateRuleOpts.CIDR/secgroups.CreateRuleOpts.FromGroupID"
+ return nil, err
}
+ rule := make(map[string]interface{})
+
rule["parent_group_id"] = opts.ParentGroupID
rule["from_port"] = opts.FromPort
rule["to_port"] = opts.ToPort
diff --git a/openstack/compute/v2/extensions/servergroups/requests.go b/openstack/compute/v2/extensions/servergroups/requests.go
index e3b2493..212edac 100644
--- a/openstack/compute/v2/extensions/servergroups/requests.go
+++ b/openstack/compute/v2/extensions/servergroups/requests.go
@@ -1,8 +1,6 @@
package servergroups
import (
- "errors"
-
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
@@ -32,11 +30,17 @@
// ToServerGroupCreateMap constructs a request body from CreateOpts.
func (opts CreateOpts) ToServerGroupCreateMap() (map[string]interface{}, error) {
if opts.Name == "" {
- return nil, errors.New("Missing field required for server group creation: Name")
+ err := gophercloud.ErrMissingInput{}
+ err.Function = "servergroups.ToServerGroupCreateMap"
+ err.Argument = "servergroups.CreateOpts.Name"
+ return nil, err
}
if len(opts.Policies) < 1 {
- return nil, errors.New("Missing field required for server group creation: Policies")
+ err := gophercloud.ErrMissingInput{}
+ err.Function = "servergroups.ToServerGroupCreateMap"
+ err.Argument = "servergroups.CreateOpts.Policies"
+ return nil, err
}
serverGroup := make(map[string]interface{})
diff --git a/openstack/compute/v2/extensions/volumeattach/requests.go b/openstack/compute/v2/extensions/volumeattach/requests.go
index 17cb72e..c2bc2ee 100644
--- a/openstack/compute/v2/extensions/volumeattach/requests.go
+++ b/openstack/compute/v2/extensions/volumeattach/requests.go
@@ -1,8 +1,6 @@
package volumeattach
import (
- "errors"
-
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
@@ -32,7 +30,10 @@
// ToVolumeAttachmentCreateMap constructs a request body from CreateOpts.
func (opts CreateOpts) ToVolumeAttachmentCreateMap() (map[string]interface{}, error) {
if opts.VolumeID == "" {
- return nil, errors.New("Missing field required for volume attachment creation: VolumeID")
+ err := gophercloud.ErrMissingInput{}
+ err.Function = "volumeattach.ToVolumeAttachmentCreateMap"
+ err.Argument = "volumeattach.CreateOpts.VolumeID"
+ return nil, err
}
volumeAttachment := make(map[string]interface{})
diff --git a/openstack/compute/v2/flavors/requests.go b/openstack/compute/v2/flavors/requests.go
index cbd6843..6e8f003 100644
--- a/openstack/compute/v2/flavors/requests.go
+++ b/openstack/compute/v2/flavors/requests.go
@@ -1,8 +1,6 @@
package flavors
import (
- "fmt"
-
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
@@ -71,33 +69,47 @@
// IDFromName is a convienience function that returns a flavor's ID given its name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
- flavorCount := 0
- flavorID := ""
+ count := 0
+ id := ""
if name == "" {
- return "", fmt.Errorf("A flavor name must be provided.")
+ err := &gophercloud.ErrMissingInput{}
+ err.Function = "flavors.IDFromName"
+ err.Argument = "name"
+ return "", err
}
- pager := ListDetail(client, nil)
- pager.EachPage(func(page pagination.Page) (bool, error) {
- flavorList, err := ExtractFlavors(page)
- if err != nil {
- return false, err
- }
- for _, f := range flavorList {
- if f.Name == name {
- flavorCount++
- flavorID = f.ID
- }
- }
- return true, nil
- })
+ allPages, err := ListDetail(client, nil).AllPages()
+ if err != nil {
+ return "", err
+ }
- switch flavorCount {
+ all, err := ExtractFlavors(allPages)
+ if err != nil {
+ return "", err
+ }
+
+ for _, f := range all {
+ if f.Name == name {
+ count++
+ id = f.ID
+ }
+ }
+
+ switch count {
case 0:
- return "", fmt.Errorf("Unable to find flavor: %s", name)
+ err := &gophercloud.ErrResourceNotFound{}
+ err.Function = "flavors.IDFromName"
+ err.ResourceType = "flavor"
+ err.Name = name
+ return "", err
case 1:
- return flavorID, nil
+ return id, nil
default:
- return "", fmt.Errorf("Found %d flavors matching %s", flavorCount, name)
+ err := &gophercloud.ErrMultipleResourcesFound{}
+ err.Function = "flavors.IDFromName"
+ err.ResourceType = "flavor"
+ err.Name = name
+ err.Count = count
+ return "", err
}
}
diff --git a/openstack/compute/v2/images/requests.go b/openstack/compute/v2/images/requests.go
index aa09959..88db657 100644
--- a/openstack/compute/v2/images/requests.go
+++ b/openstack/compute/v2/images/requests.go
@@ -1,8 +1,6 @@
package images
import (
- "fmt"
-
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
@@ -75,35 +73,47 @@
// IDFromName is a convienience function that returns an image's ID given its name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
- imageCount := 0
- imageID := ""
+ count := 0
+ id := ""
if name == "" {
- return "", fmt.Errorf("An image name must be provided.")
+ err := &gophercloud.ErrMissingInput{}
+ err.Function = "images.IDFromName"
+ err.Argument = "name"
+ return "", err
}
- pager := ListDetail(client, &ListOpts{
- Name: name,
- })
- pager.EachPage(func(page pagination.Page) (bool, error) {
- imageList, err := ExtractImages(page)
- if err != nil {
- return false, err
- }
- for _, i := range imageList {
- if i.Name == name {
- imageCount++
- imageID = i.ID
- }
- }
- return true, nil
- })
+ allPages, err := ListDetail(client, nil).AllPages()
+ if err != nil {
+ return "", err
+ }
- switch imageCount {
+ all, err := ExtractImages(allPages)
+ if err != nil {
+ return "", err
+ }
+
+ for _, f := range all {
+ if f.Name == name {
+ count++
+ id = f.ID
+ }
+ }
+
+ switch count {
case 0:
- return "", fmt.Errorf("Unable to find image: %s", name)
+ err := &gophercloud.ErrResourceNotFound{}
+ err.Function = "images.IDFromName"
+ err.ResourceType = "image"
+ err.Name = name
+ return "", err
case 1:
- return imageID, nil
+ return id, nil
default:
- return "", fmt.Errorf("Found %d images matching %s", imageCount, name)
+ err := &gophercloud.ErrMultipleResourcesFound{}
+ err.Function = "images.IDFromName"
+ err.ResourceType = "image"
+ err.Name = name
+ err.Count = count
+ return "", err
}
}
diff --git a/openstack/compute/v2/servers/errors.go b/openstack/compute/v2/servers/errors.go
index ebfcdcd..c9f0e3c 100644
--- a/openstack/compute/v2/servers/errors.go
+++ b/openstack/compute/v2/servers/errors.go
@@ -8,7 +8,7 @@
// ErrNeitherImageIDNorImageNameProvided is the error when neither the image
// ID nor the image name is provided for a server operation
-type ErrNeitherImageIDNorImageNameProvided struct{ *gophercloud.ErrMissingInput }
+type ErrNeitherImageIDNorImageNameProvided struct{ gophercloud.ErrMissingInput }
func (e ErrNeitherImageIDNorImageNameProvided) Error() string {
return "One and only one of the image ID and the image name must be provided."
@@ -16,40 +16,46 @@
// ErrNeitherFlavorIDNorFlavorNameProvided is the error when neither the flavor
// ID nor the flavor name is provided for a server operation
-type ErrNeitherFlavorIDNorFlavorNameProvided struct{ *gophercloud.ErrMissingInput }
+type ErrNeitherFlavorIDNorFlavorNameProvided struct{ gophercloud.ErrMissingInput }
func (e ErrNeitherFlavorIDNorFlavorNameProvided) Error() string {
return "One and only one of the flavor ID and the flavor name must be provided."
}
+type ErrNoClientProvidedForIDByName struct{ gophercloud.ErrMissingInput }
+
+func (e ErrNoClientProvidedForIDByName) Error() string {
+ return "A service client must be provided to find a resource ID by name."
+}
+
// ErrInvalidHowParameterProvided is the error when an unknown value is given
// for the `how` argument
-type ErrInvalidHowParameterProvided struct{ *gophercloud.ErrInvalidInput }
+type ErrInvalidHowParameterProvided struct{ gophercloud.ErrInvalidInput }
// ErrNoAdminPassProvided is the error when an administrative password isn't
// provided for a server operation
-type ErrNoAdminPassProvided struct{ *gophercloud.ErrMissingInput }
+type ErrNoAdminPassProvided struct{ gophercloud.ErrMissingInput }
// ErrNoImageIDProvided is the error when an image ID isn't provided for a server
// operation
-type ErrNoImageIDProvided struct{ *gophercloud.ErrMissingInput }
+type ErrNoImageIDProvided struct{ gophercloud.ErrMissingInput }
// ErrNoIDProvided is the error when a server ID isn't provided for a server
// operation
-type ErrNoIDProvided struct{ *gophercloud.ErrMissingInput }
+type ErrNoIDProvided struct{ gophercloud.ErrMissingInput }
// ErrServer is a generic error type for servers HTTP operations.
type ErrServer struct {
- *gophercloud.ErrUnexpectedResponseCode
+ gophercloud.ErrUnexpectedResponseCode
ID string
}
-func (se *ErrServer) Error() string {
+func (se ErrServer) Error() string {
return fmt.Sprintf("Error while executing HTTP request for server [%s]", se.ID)
}
// Error404 overrides the generic 404 error message.
-func (se *ErrServer) Error404(e *gophercloud.ErrUnexpectedResponseCode) error {
+func (se ErrServer) Error404(e gophercloud.ErrUnexpectedResponseCode) error {
se.ErrUnexpectedResponseCode = e
return &ErrServerNotFound{se}
}
@@ -57,9 +63,9 @@
// ErrServerNotFound is the error when a 404 is received during server HTTP
// operations.
type ErrServerNotFound struct {
- *ErrServer
+ ErrServer
}
-func (e *ErrServerNotFound) Error() string {
+func (e ErrServerNotFound) Error() string {
return fmt.Sprintf("I couldn't find server [%s]", e.ID)
}
diff --git a/openstack/compute/v2/servers/requests.go b/openstack/compute/v2/servers/requests.go
index c6d3a8e..488829e 100644
--- a/openstack/compute/v2/servers/requests.go
+++ b/openstack/compute/v2/servers/requests.go
@@ -4,7 +4,6 @@
"encoding/base64"
"encoding/json"
"errors"
- "fmt"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
@@ -175,7 +174,7 @@
ConfigDrive bool
// AdminPass [optional] sets the root user password. If not set, a randomly-generated
- // password will be created and returned in the response.
+ // password will be created and returned in the rponse.
AdminPass string
// AccessIPv4 [optional] specifies an IPv4 address for the instance.
@@ -252,10 +251,16 @@
// If ImageRef isn't provided, use ImageName to ascertain the image ID.
if opts.ImageRef == "" {
if opts.ImageName == "" {
- return nil, errors.New("One and only one of ImageRef and ImageName must be provided.")
+ err := ErrNeitherImageIDNorImageNameProvided{}
+ err.Function = "servers.CreateOpts.ToServerCreateMap"
+ err.Argument = "ImageRef/ImageName"
+ return nil, err
}
if opts.ServiceClient == nil {
- return nil, errors.New("A service client must be provided to find an image ID by name.")
+ err := ErrNoClientProvidedForIDByName{}
+ err.Function = "servers.CreateOpts.ToServerCreateMap"
+ err.Argument = "ServiceClient"
+ return nil, err
}
imageID, err := images.IDFromName(opts.ServiceClient, opts.ImageName)
if err != nil {
@@ -267,10 +272,16 @@
// If FlavorRef isn't provided, use FlavorName to ascertain the flavor ID.
if opts.FlavorRef == "" {
if opts.FlavorName == "" {
- return nil, errors.New("One and only one of FlavorRef and FlavorName must be provided.")
+ err := ErrNeitherFlavorIDNorFlavorNameProvided{}
+ err.Function = "servers.CreateOpts.ToServerCreateMap"
+ err.Argument = "FlavorRef/FlavorName"
+ return nil, err
}
if opts.ServiceClient == nil {
- return nil, errors.New("A service client must be provided to find a flavor ID by name.")
+ err := ErrNoClientProvidedForIDByName{}
+ err.Argument = "ServiceClient"
+ err.Function = "servers.CreateOpts.ToServerCreateMap"
+ return nil, err
}
flavorID, err := flavors.IDFromName(opts.ServiceClient, opts.FlavorName)
if err != nil {
@@ -284,23 +295,23 @@
// Create requests a server to be provisioned to the user in the current tenant.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
- var res CreateResult
+ var r CreateResult
reqBody, err := opts.ToServerCreateMap()
if err != nil {
- res.Err = err
- return res
+ r.Err = err
+ return r
}
- _, res.Err = client.Post(listURL(client), reqBody, &res.Body, nil)
- return res
+ _, r.Err = client.Post(listURL(client), reqBody, &r.Body, nil)
+ return r
}
// Delete requests that a server previously provisioned be removed from your account.
func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
- var res DeleteResult
- _, res.Err = client.Delete(deleteURL(client, id), nil)
- return res
+ var r DeleteResult
+ _, r.Err = client.Delete(deleteURL(client, id), nil)
+ return r
}
func ForceDelete(client *gophercloud.ServiceClient, id string) ActionResult {
@@ -308,19 +319,19 @@
ForceDelete string `json:"forceDelete"`
}
- var res ActionResult
- _, res.Err = client.Post(actionURL(client, id), req, nil, nil)
- return res
+ var r ActionResult
+ _, r.Err = client.Post(actionURL(client, id), req, nil, nil)
+ return r
}
// Get requests details on a single server, by ID.
func Get(client *gophercloud.ServiceClient, id string) GetResult {
- var result GetResult
- _, result.Err = client.Get(getURL(client, id), &result.Body, &gophercloud.RequestOpts{
+ var r GetResult
+ _, r.Err = client.Get(getURL(client, id), &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 203},
})
- return result
+ return r
}
// UpdateOptsBuilder allows extensions to add additional attributes to the Update request.
@@ -359,12 +370,12 @@
// Update requests that various attributes of the indicated server be changed.
func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
- var result UpdateResult
- reqBody := opts.ToServerUpdateMap()
- _, result.Err = client.Put(updateURL(client, id), reqBody, &result.Body, &gophercloud.RequestOpts{
+ var r UpdateResult
+ b := opts.ToServerUpdateMap()
+ _, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
- return result
+ return r
}
// ChangeAdminPassword alters the administrator or root password for a specified server.
@@ -377,35 +388,9 @@
req.ChangePassword.AdminPass = newPassword
- var res ActionResult
- _, res.Err = client.Post(actionURL(client, id), req, nil, nil)
- return res
-}
-
-// 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()
+ var r ActionResult
+ _, r.Err = client.Post(actionURL(client, id), req, nil, nil)
+ return r
}
// RebootMethod describes the mechanisms by which a server reboot can be requested.
@@ -420,36 +405,53 @@
PowerCycle = HardReboot
)
+type RebootOptsBuilder interface {
+ ToServerRebootMap() (map[string]interface{}, error)
+}
+
+type RebootOpts struct {
+ Type RebootMethod
+}
+
+func (opts *RebootOpts) ToServerRebootMap() (map[string]interface{}, error) {
+ if (opts.Type != SoftReboot) && (opts.Type != HardReboot) {
+ err := &gophercloud.ErrInvalidInput{}
+ err.Argument = "servers.RebootOpts.Type"
+ err.Value = opts.Type
+ err.Function = "servers.Reboot"
+ return nil, err
+ }
+
+ reqBody := map[string]interface{}{
+ "reboot": map[string]interface{}{
+ "type": string(opts.Type),
+ },
+ }
+
+ return reqBody, nil
+}
+
// 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,
+// HardReboot (aka PowerCycle) rtarts 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.
+// Then, after a brief while, power is rtored or the VM instance rtarted.
//
-// 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) ActionResult {
- var res ActionResult
+// SoftReboot (aka OSReboot) simply tells the OS to rtart under its own procedur.
+// E.g., in Linux, asking it to enter runlevel 6, or executing "sudo shutdown -r now", or by asking Windows to rtart the machine.
+func Reboot(client *gophercloud.ServiceClient, id string, opts RebootOptsBuilder) ActionResult {
+ var r ActionResult
- if (how != SoftReboot) && (how != HardReboot) {
- res.Err = &ErrArgument{
- Function: "Reboot",
- Argument: "how",
- Value: how,
- }
- return res
+ reqBody, err := opts.ToServerRebootMap()
+ if err != nil {
+ r.Err = err
+ return r
}
- reqBody := struct {
- C map[string]string `json:"reboot"`
- }{
- map[string]string{"type": string(how)},
- }
-
- _, res.Err = client.Post(actionURL(client, id), reqBody, nil, nil)
- return res
+ _, r.Err = client.Post(actionURL(client, id), reqBody, nil, nil)
+ return r
}
// RebuildOptsBuilder is an interface that allows extensions to override the
@@ -490,11 +492,17 @@
server := make(map[string]interface{})
if opts.AdminPass == "" {
- err = fmt.Errorf("AdminPass is required")
+ err := ErrNoAdminPassProvided{}
+ err.Function = "servers.ToServerRebuildMap"
+ err.Argument = "AdminPass"
+ return nil, err
}
if opts.ImageID == "" {
- err = fmt.Errorf("ImageID is required")
+ err := ErrNoImageIDProvided{}
+ err.Function = "servers.ToServerRebuildMap"
+ err.Argument = "ImageID"
+ return nil, err
}
if err != nil {
@@ -530,21 +538,24 @@
// Rebuild will reprovision the server according to the configuration options
// provided in the RebuildOpts struct.
func Rebuild(client *gophercloud.ServiceClient, id string, opts RebuildOptsBuilder) RebuildResult {
- var result RebuildResult
+ var r RebuildResult
if id == "" {
- result.Err = fmt.Errorf("ID is required")
- return result
+ err := ErrNoIDProvided{}
+ err.Function = "servers.Rebuild"
+ err.Argument = "id"
+ r.Err = err
+ return r
}
- reqBody, err := opts.ToServerRebuildMap()
+ b, err := opts.ToServerRebuildMap()
if err != nil {
- result.Err = err
- return result
+ r.Err = err
+ return r
}
- _, result.Err = client.Post(actionURL(client, id), reqBody, &result.Body, nil)
- return result
+ _, r.Err = client.Post(actionURL(client, id), b, &r.Body, nil)
+ return r
}
// ResizeOptsBuilder is an interface that allows extensions to override the default structure of
@@ -571,42 +582,68 @@
// 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.
+// Unfortunately, one cannot pass rebuild parameters to the rize function.
+// When the rize 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.
+// If you like it, call ConfirmResize() to commit the rize permanently.
+// Otherwise, call RevertResize() to rtore the old configuration.
func Resize(client *gophercloud.ServiceClient, id string, opts ResizeOptsBuilder) ActionResult {
- var res ActionResult
- reqBody, err := opts.ToServerResizeMap()
- if err != nil {
- res.Err = err
- return res
+ var r ActionResult
+
+ if id == "" {
+ err := ErrNoIDProvided{}
+ err.Function = "servers.Resize"
+ err.Argument = "id"
+ r.Err = err
+ return r
}
- _, res.Err = client.Post(actionURL(client, id), reqBody, nil, nil)
- return res
+ b, err := opts.ToServerResizeMap()
+ if err != nil {
+ r.Err = err
+ return r
+ }
+
+ _, r.Err = client.Post(actionURL(client, id), b, nil, nil)
+ return r
}
-// ConfirmResize confirms a previous resize operation on a server.
+// ConfirmResize confirms a previous rize operation on a server.
// See Resize() for more details.
func ConfirmResize(client *gophercloud.ServiceClient, id string) ActionResult {
- var res ActionResult
+ var r ActionResult
- reqBody := map[string]interface{}{"confirmResize": nil}
- _, res.Err = client.Post(actionURL(client, id), reqBody, nil, &gophercloud.RequestOpts{
+ if id == "" {
+ err := ErrNoIDProvided{}
+ err.Function = "servers.ConfirmResize"
+ err.Argument = "id"
+ r.Err = err
+ return r
+ }
+
+ b := map[string]interface{}{"confirmResize": nil}
+ _, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
OkCodes: []int{201, 202, 204},
})
- return res
+ return r
}
-// RevertResize cancels a previous resize operation on a server.
+// RevertResize cancels a previous rize operation on a server.
// See Resize() for more details.
func RevertResize(client *gophercloud.ServiceClient, id string) ActionResult {
- var res ActionResult
- reqBody := map[string]interface{}{"revertResize": nil}
- _, res.Err = client.Post(actionURL(client, id), reqBody, nil, nil)
- return res
+ var r ActionResult
+
+ if id == "" {
+ err := ErrNoIDProvided{}
+ err.Function = "servers.RevertResize"
+ err.Argument = "id"
+ r.Err = err
+ return r
+ }
+
+ b := map[string]interface{}{"revertResize": nil}
+ _, r.Err = client.Post(actionURL(client, id), b, nil, nil)
+ return r
}
// RescueOptsBuilder is an interface that allows extensions to override the
@@ -635,23 +672,27 @@
// Rescue instructs the provider to place the server into RESCUE mode.
func Rescue(client *gophercloud.ServiceClient, id string, opts RescueOptsBuilder) RescueResult {
- var result RescueResult
+ var r RescueResult
if id == "" {
- result.Err = fmt.Errorf("ID is required")
- return result
- }
- reqBody, err := opts.ToServerRescueMap()
- if err != nil {
- result.Err = err
- return result
+ err := ErrNoIDProvided{}
+ err.Function = "servers.Rebuild"
+ err.Argument = "id"
+ r.Err = err
+ return r
}
- _, result.Err = client.Post(actionURL(client, id), reqBody, &result.Body, &gophercloud.RequestOpts{
+ b, err := opts.ToServerRescueMap()
+ if err != nil {
+ r.Err = err
+ return r
+ }
+
+ _, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
- return result
+ return r
}
// ResetMetadataOptsBuilder allows extensions to add additional parameters to the
@@ -678,23 +719,23 @@
// the new metadata provided. To keep any already-existing metadata, use the
// UpdateMetadatas or UpdateMetadata function.
func ResetMetadata(client *gophercloud.ServiceClient, id string, opts ResetMetadataOptsBuilder) ResetMetadataResult {
- var res ResetMetadataResult
+ var r ResetMetadataResult
metadata, err := opts.ToMetadataResetMap()
if err != nil {
- res.Err = err
- return res
+ r.Err = err
+ return r
}
- _, res.Err = client.Put(metadataURL(client, id), metadata, &res.Body, &gophercloud.RequestOpts{
+ _, r.Err = client.Put(metadataURL(client, id), metadata, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
- return res
+ return r
}
// Metadata requests all the metadata for the given server ID.
func Metadata(client *gophercloud.ServiceClient, id string) GetMetadataResult {
- var res GetMetadataResult
- _, res.Err = client.Get(metadataURL(client, id), &res.Body, nil)
- return res
+ var r GetMetadataResult
+ _, r.Err = client.Get(metadataURL(client, id), &r.Body, nil)
+ return r
}
// UpdateMetadataOptsBuilder allows extensions to add additional parameters to the
@@ -707,16 +748,16 @@
// This operation does not affect already-existing metadata that is not specified
// by opts.
func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMetadataOptsBuilder) UpdateMetadataResult {
- var res UpdateMetadataResult
+ var r UpdateMetadataResult
metadata, err := opts.ToMetadataUpdateMap()
if err != nil {
- res.Err = err
- return res
+ r.Err = err
+ return r
}
- _, res.Err = client.Post(metadataURL(client, id), metadata, &res.Body, &gophercloud.RequestOpts{
+ _, r.Err = client.Post(metadataURL(client, id), metadata, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
- return res
+ return r
}
// MetadatumOptsBuilder allows extensions to add additional parameters to the
@@ -743,33 +784,33 @@
// CreateMetadatum will create or update the key-value pair with the given key for the given server ID.
func CreateMetadatum(client *gophercloud.ServiceClient, id string, opts MetadatumOptsBuilder) CreateMetadatumResult {
- var res CreateMetadatumResult
+ var r CreateMetadatumResult
metadatum, key, err := opts.ToMetadatumCreateMap()
if err != nil {
- res.Err = err
- return res
+ r.Err = err
+ return r
}
- _, res.Err = client.Put(metadatumURL(client, id, key), metadatum, &res.Body, &gophercloud.RequestOpts{
+ _, r.Err = client.Put(metadatumURL(client, id, key), metadatum, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
- return res
+ return r
}
// Metadatum requests the key-value pair with the given key for the given server ID.
func Metadatum(client *gophercloud.ServiceClient, id, key string) GetMetadatumResult {
- var res GetMetadatumResult
- _, res.Err = client.Request("GET", metadatumURL(client, id, key), &gophercloud.RequestOpts{
- JSONResponse: &res.Body,
+ var r GetMetadatumResult
+ _, r.Err = client.Request("GET", metadatumURL(client, id, key), &gophercloud.RequestOpts{
+ JSONResponse: &r.Body,
})
- return res
+ return r
}
// DeleteMetadatum will delete the key-value pair with the given key for the given server ID.
func DeleteMetadatum(client *gophercloud.ServiceClient, id, key string) DeleteMetadatumResult {
- var res DeleteMetadatumResult
- _, res.Err = client.Delete(metadatumURL(client, id, key), nil)
- return res
+ var r DeleteMetadatumResult
+ _, r.Err = client.Delete(metadatumURL(client, id, key), nil)
+ return r
}
// ListAddresses makes a request against the API to list the servers IP addresses.
@@ -805,7 +846,10 @@
var err error
img := make(map[string]interface{})
if opts.Name == "" {
- return nil, fmt.Errorf("Cannot create a server image without a name")
+ err := gophercloud.ErrMissingInput{}
+ err.Function = "servers.CreateImageOpts.ToServerCreateImageMap"
+ err.Argument = "CreateImageOpts.Name"
+ return nil, err
}
img["name"] = opts.Name
if opts.Metadata != nil {
@@ -818,49 +862,63 @@
// CreateImage makes a request against the nova API to schedule an image to be created of the server
func CreateImage(client *gophercloud.ServiceClient, serverID string, opts CreateImageOptsBuilder) CreateImageResult {
- var res CreateImageResult
- reqBody, err := opts.ToServerCreateImageMap()
+ var r CreateImageResult
+ b, err := opts.ToServerCreateImageMap()
if err != nil {
- res.Err = err
- return res
+ r.Err = err
+ return r
}
- response, err := client.Post(actionURL(client, serverID), reqBody, nil, &gophercloud.RequestOpts{
+ resp, err := client.Post(actionURL(client, serverID), b, nil, &gophercloud.RequestOpts{
OkCodes: []int{202},
})
- res.Err = err
- res.Header = response.Header
- return res
+ r.Err = err
+ r.Header = resp.Header
+ return r
}
// IDFromName is a convienience function that returns a server's ID given its name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
- serverCount := 0
- serverID := ""
+ count := 0
+ id := ""
if name == "" {
- return "", fmt.Errorf("A server name must be provided.")
+ err := &gophercloud.ErrMissingInput{}
+ err.Function = "servers.IDFromName"
+ err.Argument = "name"
+ return "", err
}
- pager := List(client, nil)
- pager.EachPage(func(page pagination.Page) (bool, error) {
- serverList, err := ExtractServers(page)
- if err != nil {
- return false, err
- }
- for _, s := range serverList {
- if s.Name == name {
- serverCount++
- serverID = s.ID
- }
- }
- return true, nil
- })
+ allPages, err := List(client, nil).AllPages()
+ if err != nil {
+ return "", err
+ }
- switch serverCount {
+ all, err := ExtractServers(allPages)
+ if err != nil {
+ return "", err
+ }
+
+ for _, f := range all {
+ if f.Name == name {
+ count++
+ id = f.ID
+ }
+ }
+
+ switch count {
case 0:
- return "", fmt.Errorf("Unable to find server: %s", name)
+ err := &gophercloud.ErrResourceNotFound{}
+ err.Function = "servers.IDFromName"
+ err.ResourceType = "server"
+ err.Name = name
+ return "", err
case 1:
- return serverID, nil
+ return id, nil
default:
- return "", fmt.Errorf("Found %d servers matching %s", serverCount, name)
+ err := &gophercloud.ErrMultipleResourcesFound{}
+ err.Function = "servers.IDFromName"
+ err.ResourceType = "server"
+ err.Name = name
+ err.Count = count
+ return "", err
}
}
diff --git a/openstack/compute/v2/servers/requests_test.go b/openstack/compute/v2/servers/requests_test.go
index 39abdd7..73d1375 100644
--- a/openstack/compute/v2/servers/requests_test.go
+++ b/openstack/compute/v2/servers/requests_test.go
@@ -129,7 +129,9 @@
defer th.TeardownHTTP()
HandleRebootSuccessfully(t)
- res := Reboot(client.ServiceClient(), "1234asdf", SoftReboot)
+ res := Reboot(client.ServiceClient(), "1234asdf", &RebootOpts{
+ Type: SoftReboot,
+ })
th.AssertNoErr(t, res.Err)
}