feature/shared file systems: create share (#119)

* sfs/add url for create

* sfs/results: add share type and result type

* sfs/requests: add CreateOpts and Create method

* sfs/tests: add tests for creating a share

* sfs/shares: fixes according to comments

* sfs/create: fix Metadata field naming in Share
diff --git a/openstack/sharedfilesystems/v2/shares/requests.go b/openstack/sharedfilesystems/v2/shares/requests.go
new file mode 100644
index 0000000..f168178
--- /dev/null
+++ b/openstack/sharedfilesystems/v2/shares/requests.go
@@ -0,0 +1,69 @@
+package shares
+
+import (
+	"github.com/gophercloud/gophercloud"
+)
+
+// CreateOptsBuilder allows extensions to add additional parameters to the
+// Create request.
+type CreateOptsBuilder interface {
+	ToShareCreateMap() (map[string]interface{}, error)
+}
+
+// CreateOpts contains the options for create a Share. This object is
+// passed to shares.Create(). For more information about these parameters,
+// please refer to the Share object, or the shared file systems API v2
+// documentation
+type CreateOpts struct {
+	// Defines the share protocol to use
+	ShareProto string `json:"share_proto" required:"true"`
+	// Size in GB
+	Size int `json:"size" required:"true"`
+	// Defines the share name
+	Name string `json:"name,omitempty"`
+	// Share description
+	Description string `json:"description,omitempty"`
+	// DisplayName is equivalent to Name. The API supports using both
+	// This is an inherited attribute from the block storage API
+	DisplayName string `json:"display_name,omitempty"`
+	// DisplayDescription is equivalent to Description. The API supports using bot
+	// This is an inherited attribute from the block storage API
+	DisplayDescription string `json:"display_description,omitempty"`
+	// ShareType defines the sharetype. If omitted, a default share type is used
+	ShareType string `json:"share_type,omitempty"`
+	// VolumeType is deprecated but supported. Either ShareType or VolumeType can be used
+	VolumeType string `json:"volume_type,omitempty"`
+	// The UUID from which to create a share
+	SnapshotID string `json:"snapshot_id,omitempty"`
+	// Determines whether or not the share is public
+	IsPublic *bool `json:"is_public,omitempty"`
+	// Key value pairs of user defined metadata
+	Metadata map[string]string `json:"metadata,omitempty"`
+	// The UUID of the share network to which the share belongs to
+	ShareNetworkID string `json:"share_network_id,omitempty"`
+	// The UUID of the consistency group to which the share belongs to
+	ConsistencyGroupID string `json:"consistency_group_id,omitempty"`
+	// The availability zone of the share
+	AvailabilityZone string `json:"availability_zone,omitempty"`
+}
+
+// ToShareCreateMap assembles a request body based on the contents of a
+// CreateOpts.
+func (opts CreateOpts) ToShareCreateMap() (map[string]interface{}, error) {
+	return gophercloud.BuildRequestBody(opts, "share")
+}
+
+// Create will create a new Share based on the values in CreateOpts. To extract
+// the Share object from the response, call the Extract method on the
+// CreateResult.
+func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
+	b, err := opts.ToShareCreateMap()
+	if err != nil {
+		r.Err = err
+		return
+	}
+	_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
+		OkCodes: []int{200, 201},
+	})
+	return
+}
diff --git a/openstack/sharedfilesystems/v2/shares/results.go b/openstack/sharedfilesystems/v2/shares/results.go
new file mode 100644
index 0000000..f303201
--- /dev/null
+++ b/openstack/sharedfilesystems/v2/shares/results.go
@@ -0,0 +1,82 @@
+package shares
+
+import (
+	"github.com/gophercloud/gophercloud"
+)
+
+// Share contains all information associated with an OpenStack Share
+type Share struct {
+	// The availability zone of the share
+	AvailabilityZone string `json:"availability_zone"`
+	// A description of the share
+	Description string `json:"description,omitempty"`
+	// DisplayDescription is inherited from BlockStorage API.
+	// Both Description and DisplayDescription can be used
+	DisplayDescription string `json:"display_description,omitempty"`
+	// DisplayName is inherited from BlockStorage API
+	// Both DisplayName and Name can be used
+	DisplayName string `json:"display_name,omitempty"`
+	// Indicates whether a share has replicas or not.
+	HasReplicas bool `json:"has_replicas"`
+	// The host name of the share
+	Host string `json:"host"`
+	// The UUID of the share
+	ID string `json:"id"`
+	// Indicates the visibility of the share
+	IsPublic bool `json:"is_public,omitempty"`
+	// Share links for pagination
+	Links []map[string]string `json:"links"`
+	// Key, value -pairs of custom metadata
+	Metadata map[string]string `json:"metadata,omitempty"`
+	// The name of the share
+	Name string `json:"name,omitempty"`
+	// The UUID of the project to which this share belongs to
+	ProjectID string `json:"project_id"`
+	// The share replication type
+	ReplicationType string `json:"replication_type,omitempty"`
+	// The UUID of the share network
+	ShareNetworkID string `json:"share_network_id"`
+	// The shared file system protocol
+	ShareProto string `json:"share_proto"`
+	// The UUID of the share server
+	ShareServerID string `json:"share_server_id"`
+	// The UUID of the share type.
+	ShareType string `json:"share_type"`
+	// The name of the share type.
+	ShareTypeName string `json:"share_type_name"`
+	// Size of the share in GB
+	Size int `json:"size"`
+	// UUID of the snapshot from which to create the share
+	SnapshotID string `json:"snapshot_id"`
+	// The share status
+	Status string `json:"status"`
+	// The task state, used for share migration
+	TaskState string `json:"task_state"`
+	// The type of the volume
+	VolumeType string `json:"volume_type,omitempty"`
+	// The UUID of the consistency group this share belongs to
+	ConsistencyGroupID string `json:"consistency_group_id"`
+	// Used for filtering backends which either support or do not support share snapshots
+	SnapshotSupport          bool   `json:"snapshot_support"`
+	SourceCgsnapshotMemberID string `json:"source_cgsnapshot_member_id"`
+	// Timestamp when the share was created
+	CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"`
+}
+
+type commonResult struct {
+	gophercloud.Result
+}
+
+// Extract will get the Share object from the commonResult
+func (r commonResult) Extract() (*Share, error) {
+	var s struct {
+		Share *Share `json:"share"`
+	}
+	err := r.ExtractInto(&s)
+	return s.Share, err
+}
+
+// CreateResult contains the result..
+type CreateResult struct {
+	commonResult
+}
diff --git a/openstack/sharedfilesystems/v2/shares/testing/fixtures.go b/openstack/sharedfilesystems/v2/shares/testing/fixtures.go
new file mode 100644
index 0000000..3f90946
--- /dev/null
+++ b/openstack/sharedfilesystems/v2/shares/testing/fixtures.go
@@ -0,0 +1,80 @@
+package testing
+
+import (
+	"fmt"
+	th "github.com/gophercloud/gophercloud/testhelper"
+	fake "github.com/gophercloud/gophercloud/testhelper/client"
+	"net/http"
+	"testing"
+)
+
+const (
+	shareEndpoint = "/shares"
+)
+
+var createRequest = `{
+		"share": {
+			"name": "my_test_share",
+			"size": 1,
+			"share_proto": "NFS"
+		}
+	}`
+
+var createResponse = `{
+		"share": {
+			"name": "my_test_share",
+			"share_proto": "NFS",
+			"size": 1,
+			"status": null,
+			"share_server_id": null,
+			"project_id": "16e1ab15c35a457e9c2b2aa189f544e1",
+			"share_type": "25747776-08e5-494f-ab40-a64b9d20d8f7",
+			"share_type_name": "default",
+			"availability_zone": null,
+			"created_at": "2015-09-18T10:25:24.533287",
+			"export_location": null,
+			"links": [
+				{
+					"href": "http://172.18.198.54:8786/v1/16e1ab15c35a457e9c2b2aa189f544e1/shares/011d21e2-fbc3-4e4a-9993-9ea223f73264",
+					"rel": "self"
+				},
+				{
+					"href": "http://172.18.198.54:8786/16e1ab15c35a457e9c2b2aa189f544e1/shares/011d21e2-fbc3-4e4a-9993-9ea223f73264",
+					"rel": "bookmark"
+				}
+			],
+			"share_network_id": null,
+			"export_locations": [],
+			"host": null,
+			"access_rules_status": "active",
+			"has_replicas": false,
+			"replication_type": null,
+			"task_state": null,
+			"snapshot_support": true,
+			"consistency_group_id": "9397c191-8427-4661-a2e8-b23820dc01d4",
+			"source_cgsnapshot_member_id": null,
+			"volume_type": "default",
+			"snapshot_id": null,
+			"is_public": true,
+			"metadata": {
+				"project": "my_app",
+				"aim": "doc"
+			},
+			"id": "011d21e2-fbc3-4e4a-9993-9ea223f73264",
+			"description": "My custom share London"
+		}
+	}`
+
+// MockCreateResponse creates a mock response
+func MockCreateResponse(t *testing.T) {
+	th.Mux.HandleFunc(shareEndpoint, func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "POST")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Content-Type", "application/json")
+		th.TestHeader(t, r, "Accept", "application/json")
+		th.TestJSONRequest(t, r, createRequest)
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprintf(w, createResponse)
+	})
+}
diff --git a/openstack/sharedfilesystems/v2/shares/testing/request_test.go b/openstack/sharedfilesystems/v2/shares/testing/request_test.go
new file mode 100644
index 0000000..6d69201
--- /dev/null
+++ b/openstack/sharedfilesystems/v2/shares/testing/request_test.go
@@ -0,0 +1,23 @@
+package testing
+
+import (
+	"github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares"
+	th "github.com/gophercloud/gophercloud/testhelper"
+	"github.com/gophercloud/gophercloud/testhelper/client"
+	"testing"
+)
+
+func TestCreate(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	MockCreateResponse(t)
+
+	options := &shares.CreateOpts{Size: 1, Name: "my_test_share", ShareProto: "NFS"}
+	n, err := shares.Create(client.ServiceClient(), options).Extract()
+
+	th.AssertNoErr(t, err)
+	th.AssertEquals(t, n.Name, "my_test_share")
+	th.AssertEquals(t, n.Size, 1)
+	th.AssertEquals(t, n.ShareProto, "NFS")
+}
diff --git a/openstack/sharedfilesystems/v2/shares/urls.go b/openstack/sharedfilesystems/v2/shares/urls.go
new file mode 100644
index 0000000..4fba746
--- /dev/null
+++ b/openstack/sharedfilesystems/v2/shares/urls.go
@@ -0,0 +1,7 @@
+package shares
+
+import "github.com/gophercloud/gophercloud"
+
+func createURL(c *gophercloud.ServiceClient) string {
+	return c.ServiceURL("shares")
+}