Initial attempt at volumeattach extension
diff --git a/openstack/compute/v2/extensions/volumeattach/requests.go b/openstack/compute/v2/extensions/volumeattach/requests.go
new file mode 100644
index 0000000..ba1b7d4
--- /dev/null
+++ b/openstack/compute/v2/extensions/volumeattach/requests.go
@@ -0,0 +1,119 @@
+package volumeattach
+
+import (
+	"errors"
+
+	"github.com/racker/perigee"
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
+	"github.com/rackspace/gophercloud/pagination"
+)
+
+// CreateOptsExt adds a VolumeAttachment option to the base CreateOpts.
+type CreateOptsExt struct {
+	servers.CreateOptsBuilder
+	Device   string `json:"device"`
+	VolumeID string `json:"volumeId"`
+	ServerID string `json:"serverId"`
+}
+
+// ToServerCreateMap adds the volume_id, device, and optionally server_id to
+// the base server creation options.
+func (opts CreateOptsExt) ToServerCreateMap() (map[string]interface{}, error) {
+	base, err := opts.CreateOptsBuilder.ToServerCreateMap()
+	if err != nil {
+		return nil, err
+	}
+
+	serverMap := base["server"].(map[string]interface{})
+	serverMap["device"] = opts.Device
+	serverMap["volume_id"] = opts.VolumeID
+	serverMap["server_id"] = opts.ServerID
+
+	return base, nil
+}
+
+// List returns a Pager that allows you to iterate over a collection of VolumeAttachments.
+func List(client *gophercloud.ServiceClient, serverId string) pagination.Pager {
+	return pagination.NewPager(client, listURL(client, serverId), func(r pagination.PageResult) pagination.Page {
+		return VolumeAttachmentsPage{pagination.SinglePageBase(r)}
+	})
+}
+
+// CreateOptsBuilder describes struct types that can be accepted by the Create call. Notable, the
+// CreateOpts struct in this package does.
+type CreateOptsBuilder interface {
+	ToVolumeAttachmentCreateMap() (map[string]interface{}, error)
+}
+
+// CreateOpts specifies volume attachment creation or import parameters.
+type CreateOpts struct {
+	// Device is the device that the volume will attach to the instance as. Omit for "auto"
+	Device string
+
+	// VolumeID is the ID of the volume to attach to the instance
+	VolumeID string
+
+	// ServerID is the ID of the server that the volume will be attached to
+	ServerID string
+}
+
+// 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")
+	}
+
+	if opts.ServerID == "" {
+		return nil, errors.New("Missing field required for volume attachment creation: ServerID")
+	}
+
+	volumeAttachment := make(map[string]interface{})
+	volumeAttachment["volumeId"] = opts.VolumeID
+	volumeAttachment["serverId"] = opts.ServerID
+	if opts.Device != "" {
+		volumeAttachment["device"] = opts.Device
+	}
+
+	return map[string]interface{}{"volumeAttachment": volumeAttachment}, nil
+}
+
+// Create requests the creation of a new volume attachment on the server
+func Create(client *gophercloud.ServiceClient, serverId string, opts CreateOptsBuilder) CreateResult {
+	var res CreateResult
+
+	reqBody, err := opts.ToVolumeAttachmentCreateMap()
+	if err != nil {
+		res.Err = err
+		return res
+	}
+
+	_, res.Err = perigee.Request("POST", createURL(client, serverId), perigee.Options{
+		MoreHeaders: client.AuthenticatedHeaders(),
+		ReqBody:     reqBody,
+		Results:     &res.Body,
+		OkCodes:     []int{200},
+	})
+	return res
+}
+
+// Get returns public data about a previously created VolumeAttachment.
+func Get(client *gophercloud.ServiceClient, serverId, aId string) GetResult {
+	var res GetResult
+	_, res.Err = perigee.Request("GET", getURL(client, serverId, aId), perigee.Options{
+		MoreHeaders: client.AuthenticatedHeaders(),
+		Results:     &res.Body,
+		OkCodes:     []int{200},
+	})
+	return res
+}
+
+// Delete requests the deletion of a previous stored VolumeAttachment from the server.
+func Delete(client *gophercloud.ServiceClient, serverId, aId string) DeleteResult {
+	var res DeleteResult
+	_, res.Err = perigee.Request("DELETE", deleteURL(client, serverId, aId), perigee.Options{
+		MoreHeaders: client.AuthenticatedHeaders(),
+		OkCodes:     []int{202},
+	})
+	return res
+}