Feature/filestorage sharetype setextraspecs (#147)

* sfs: Add support for share type Set Extra Specs

* sfs: Add acceptance tests for share type Set Extra Specs

* sfs: Fix tests
diff --git a/acceptance/openstack/sharedfilesystems/v2/sharetypes_test.go b/acceptance/openstack/sharedfilesystems/v2/sharetypes_test.go
index e29e484..d54b3f7 100644
--- a/acceptance/openstack/sharedfilesystems/v2/sharetypes_test.go
+++ b/acceptance/openstack/sharedfilesystems/v2/sharetypes_test.go
@@ -73,6 +73,15 @@
 		t.Fatalf("Unable to create share type: %v", err)
 	}
 
+	options := sharetypes.SetExtraSpecsOpts{
+		Specs: map[string]interface{}{"my_new_key": "my_value"},
+	}
+
+	_, err = sharetypes.SetExtraSpecs(client, shareType.ID, options).Extract()
+	if err != nil {
+		t.Fatalf("Unable to set extra specs for Share type: %s", shareType.Name)
+	}
+
 	extraSpecs, err := sharetypes.GetExtraSpecs(client, shareType.ID).Extract()
 	if err != nil {
 		t.Fatalf("Unable to retrieve share type: %s", shareType.Name)
@@ -82,6 +91,10 @@
 		t.Fatal("driver_handles_share_servers was expected to be true")
 	}
 
+	if extraSpecs["my_new_key"] != "my_value" {
+		t.Fatal("my_new_key was expected to be equal to my_value")
+	}
+
 	PrintShareType(t, shareType)
 
 	defer DeleteShareType(t, client, shareType)
diff --git a/openstack/sharedfilesystems/v2/sharetypes/requests.go b/openstack/sharedfilesystems/v2/sharetypes/requests.go
index 94f84b6..0b6744b 100644
--- a/openstack/sharedfilesystems/v2/sharetypes/requests.go
+++ b/openstack/sharedfilesystems/v2/sharetypes/requests.go
@@ -104,3 +104,36 @@
 	_, r.Err = client.Get(getExtraSpecsURL(client, id), &r.Body, nil)
 	return
 }
+
+// SetExtraSpecsOptsBuilder allows extensions to add additional parameters to the
+// SetExtraSpecs request.
+type SetExtraSpecsOptsBuilder interface {
+	ToShareTypeSetExtraSpecsMap() (map[string]interface{}, error)
+}
+
+type SetExtraSpecsOpts struct {
+	// A list of all extra specifications to be added to a ShareType
+	Specs map[string]interface{} `json:"extra_specs"`
+}
+
+// ToShareTypeSetExtraSpecsMap assembles a request body based on the contents of a
+// SetExtraSpecsOpts.
+func (opts SetExtraSpecsOpts) ToShareTypeSetExtraSpecsMap() (map[string]interface{}, error) {
+	return gophercloud.BuildRequestBody(opts, "")
+}
+
+// SetExtraSpecs will set new specifications for a ShareType based on the values
+// in SetExtraSpecsOpts. To extract the extra specifications object from the response,
+// call the Extract method on the SetExtraSpecsResult.
+func SetExtraSpecs(client *gophercloud.ServiceClient, id string, opts SetExtraSpecsOptsBuilder) (r SetExtraSpecsResult) {
+	b, err := opts.ToShareTypeSetExtraSpecsMap()
+	if err != nil {
+		r.Err = err
+		return
+	}
+
+	_, r.Err = client.Post(setExtraSpecsURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
+		OkCodes: []int{200, 202},
+	})
+	return
+}
diff --git a/openstack/sharedfilesystems/v2/sharetypes/results.go b/openstack/sharedfilesystems/v2/sharetypes/results.go
index f4de532..9388b52 100644
--- a/openstack/sharedfilesystems/v2/sharetypes/results.go
+++ b/openstack/sharedfilesystems/v2/sharetypes/results.go
@@ -90,3 +90,8 @@
 type GetExtraSpecsResult struct {
 	extraSpecsResult
 }
+
+// SetExtraSpecsResult contains the response body and error from a Set Extra Specs request.
+type SetExtraSpecsResult struct {
+	extraSpecsResult
+}
diff --git a/openstack/sharedfilesystems/v2/sharetypes/testing/fixtures.go b/openstack/sharedfilesystems/v2/sharetypes/testing/fixtures.go
index 1666d11..6565ba5 100644
--- a/openstack/sharedfilesystems/v2/sharetypes/testing/fixtures.go
+++ b/openstack/sharedfilesystems/v2/sharetypes/testing/fixtures.go
@@ -177,7 +177,32 @@
             "extra_specs": {
                 "snapshot_support": "True",
                 "driver_handles_share_servers": "True",
-				"my_custom_extra_spec": "False"
+                "my_custom_extra_spec": "False"
+            }
+        }`)
+	})
+}
+
+func MockSetExtraSpecsResponse(t *testing.T) {
+	th.Mux.HandleFunc("/types/shareTypeID/extra_specs", 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, `
+        {
+            "extra_specs": {
+                "my_key": "my_value"
+            }
+        }`)
+
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusAccepted)
+
+		fmt.Fprintf(w, `
+        {
+            "extra_specs": {
+                "my_key": "my_value"
             }
         }`)
 	})
diff --git a/openstack/sharedfilesystems/v2/sharetypes/testing/requests_test.go b/openstack/sharedfilesystems/v2/sharetypes/testing/requests_test.go
index ad1ad15..d6576c3 100644
--- a/openstack/sharedfilesystems/v2/sharetypes/testing/requests_test.go
+++ b/openstack/sharedfilesystems/v2/sharetypes/testing/requests_test.go
@@ -134,3 +134,20 @@
 	th.AssertEquals(t, st["driver_handles_share_servers"], "True")
 	th.AssertEquals(t, st["my_custom_extra_spec"], "False")
 }
+
+// Verifies that an extra specs can be added to a share type
+func TestSetExtraSpecs(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	MockSetExtraSpecsResponse(t)
+
+	options := &sharetypes.SetExtraSpecsOpts{
+		Specs: map[string]interface{}{"my_key": "my_value"},
+	}
+
+	es, err := sharetypes.SetExtraSpecs(client.ServiceClient(), "shareTypeID", options).Extract()
+	th.AssertNoErr(t, err)
+
+	th.AssertEquals(t, es["my_key"], "my_value")
+}
diff --git a/openstack/sharedfilesystems/v2/sharetypes/urls.go b/openstack/sharedfilesystems/v2/sharetypes/urls.go
index ecd159e..848aac8 100644
--- a/openstack/sharedfilesystems/v2/sharetypes/urls.go
+++ b/openstack/sharedfilesystems/v2/sharetypes/urls.go
@@ -21,3 +21,7 @@
 func getExtraSpecsURL(c *gophercloud.ServiceClient, id string) string {
 	return c.ServiceURL("types", id, "extra_specs")
 }
+
+func setExtraSpecsURL(c *gophercloud.ServiceClient, id string) string {
+	return getExtraSpecsURL(c, id)
+}