Add/remove servers from secgroups
diff --git a/openstack/compute/v2/extensions/secgroups/fixtures.go b/openstack/compute/v2/extensions/secgroups/fixtures.go
index 1446912..da42b14 100644
--- a/openstack/compute/v2/extensions/secgroups/fixtures.go
+++ b/openstack/compute/v2/extensions/secgroups/fixtures.go
@@ -84,7 +84,7 @@
 func mockUpdateGroupResponse(t *testing.T, groupID string) {
 	url := fmt.Sprintf("%s/%s", rootPath, groupID)
 	th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
-		th.TestMethod(t, r, "POST")
+		th.TestMethod(t, r, "PUT")
 		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
 
 		th.TestJSONRequest(t, r, `
@@ -205,3 +205,41 @@
 		w.WriteHeader(http.StatusAccepted)
 	})
 }
+
+func mockAddServerToGroupResponse(t *testing.T, serverID string) {
+	url := fmt.Sprintf("/servers/%s/action", serverID)
+	th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "POST")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+		th.TestJSONRequest(t, r, `
+{
+  "addSecurityGroup": {
+    "name": "test"
+  }
+}
+	`)
+
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+	})
+}
+
+func mockRemoveServerFromGroupResponse(t *testing.T, serverID string) {
+	url := fmt.Sprintf("/servers/%s/action", serverID)
+	th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "POST")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+		th.TestJSONRequest(t, r, `
+{
+	"removeSecurityGroup": {
+		"name": "test"
+	}
+}
+	`)
+
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+	})
+}
diff --git a/openstack/compute/v2/extensions/secgroups/requests.go b/openstack/compute/v2/extensions/secgroups/requests.go
index 505439e..c33fcaf 100644
--- a/openstack/compute/v2/extensions/secgroups/requests.go
+++ b/openstack/compute/v2/extensions/secgroups/requests.go
@@ -63,7 +63,7 @@
 		UpdateOpts `json:"security_group"`
 	}{opts}
 
-	_, result.Err = perigee.Request("POST", resourceURL(client, id), perigee.Options{
+	_, result.Err = perigee.Request("PUT", resourceURL(client, id), perigee.Options{
 		Results:     &result.Body,
 		ReqBody:     &reqBody,
 		MoreHeaders: client.AuthenticatedHeaders(),
@@ -169,3 +169,35 @@
 
 	return result
 }
+
+func actionMap(prefix, groupName string) map[string]map[string]string {
+	return map[string]map[string]string{
+		prefix + "SecurityGroup": map[string]string{"name": groupName},
+	}
+}
+
+func AddServerToGroup(client *gophercloud.ServiceClient, serverID, groupName string) gophercloud.ErrResult {
+	var result gophercloud.ErrResult
+
+	_, result.Err = perigee.Request("POST", serverActionURL(client, serverID), perigee.Options{
+		Results:     &result.Body,
+		ReqBody:     actionMap("add", groupName),
+		MoreHeaders: client.AuthenticatedHeaders(),
+		OkCodes:     []int{200},
+	})
+
+	return result
+}
+
+func RemoveServerFromGroup(client *gophercloud.ServiceClient, serverID, groupName string) gophercloud.ErrResult {
+	var result gophercloud.ErrResult
+
+	_, result.Err = perigee.Request("POST", serverActionURL(client, serverID), perigee.Options{
+		Results:     &result.Body,
+		ReqBody:     actionMap("remove", groupName),
+		MoreHeaders: client.AuthenticatedHeaders(),
+		OkCodes:     []int{200},
+	})
+
+	return result
+}
diff --git a/openstack/compute/v2/extensions/secgroups/requests_test.go b/openstack/compute/v2/extensions/secgroups/requests_test.go
index 37008ac..37acd36 100644
--- a/openstack/compute/v2/extensions/secgroups/requests_test.go
+++ b/openstack/compute/v2/extensions/secgroups/requests_test.go
@@ -207,3 +207,23 @@
 	err := DeleteRule(client.ServiceClient(), ruleID).ExtractErr()
 	th.AssertNoErr(t, err)
 }
+
+func TestAddServer(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	mockAddServerToGroupResponse(t, serverID)
+
+	err := AddServerToGroup(client.ServiceClient(), serverID, "test").ExtractErr()
+	th.AssertNoErr(t, err)
+}
+
+func TestRemoveServer(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	mockRemoveServerFromGroupResponse(t, serverID)
+
+	err := RemoveServerFromGroup(client.ServiceClient(), serverID, "test").ExtractErr()
+	th.AssertNoErr(t, err)
+}
diff --git a/openstack/compute/v2/extensions/secgroups/urls.go b/openstack/compute/v2/extensions/secgroups/urls.go
index 7e2694c..f4760b6 100644
--- a/openstack/compute/v2/extensions/secgroups/urls.go
+++ b/openstack/compute/v2/extensions/secgroups/urls.go
@@ -26,3 +26,7 @@
 func resourceRuleURL(c *gophercloud.ServiceClient, id string) string {
 	return c.ServiceURL(rulepath, id)
 }
+
+func serverActionURL(c *gophercloud.ServiceClient, id string) string {
+	return c.ServiceURL("servers", id, "action")
+}