Repository: gophercloud/gophercloud

Issue:
Enable quota deletion for network and loadbalancer quotas


Discussion:

Solution patch:
diff --git a/docs/contributor-tutorial/step-04-acceptance-testing.md b/docs/contributor-tutorial/step-04-acceptance-testing.md
index fe8271743..a6708ee5f 100644
--- a/docs/contributor-tutorial/step-04-acceptance-testing.md
+++ b/docs/contributor-tutorial/step-04-acceptance-testing.md
@@ -17,9 +17,13 @@ And, to be frank, submitting untested code will inevitably cause someone else
 to have to spend time fixing it.
 
 If you don't have an OpenStack environment to test with, we have lots of
-documentation [here](/acceptance) to help you build your own small OpenStack
+documentation [here](/internal/acceptance) to help you build your own small OpenStack
 environment for testing.
 
+If you add new API calls/actions e.g. also make sure to include them into the
+respective [acceptance tests](/internal/acceptance/openstack) for the code to be
+validated against all the supported OpenStack versions.
+
 ---
 
 Once you've confirmed you are able to test your code, proceed to
diff --git a/openstack/loadbalancer/v2/quotas/doc.go b/openstack/loadbalancer/v2/quotas/doc.go
index c2c63512c..77c560022 100644
--- a/openstack/loadbalancer/v2/quotas/doc.go
+++ b/openstack/loadbalancer/v2/quotas/doc.go
@@ -30,5 +30,15 @@ Example to Update project quotas
 	    }
 
 	    fmt.Printf("quotas: %#v\n", quotasInfo)
+
+Example to Delete project quotas
+
+	projectID = "23d5d3f79dfa4f73b72b8b0b0063ec55"
+	err := quotas.Delete(context.TODO(), networkClient, projectID).ExtractErr()
+	if err != nil {
+	    log.Fatal(err)
+	}
+
+	fmt.Printf("Deleted quotas for project: %s\n", projectID)
 */
 package quotas
diff --git a/openstack/loadbalancer/v2/quotas/requests.go b/openstack/loadbalancer/v2/quotas/requests.go
index 8b7771eea..f07f9392e 100644
--- a/openstack/loadbalancer/v2/quotas/requests.go
+++ b/openstack/loadbalancer/v2/quotas/requests.go
@@ -63,3 +63,9 @@ func Update(ctx context.Context, c *gophercloud.ServiceClient, projectID string,
 	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
 	return
 }
+
+func Delete(ctx context.Context, c *gophercloud.ServiceClient, projectID string) (r DeleteResult) {
+	resp, err := c.Delete(ctx, deleteURL(c, projectID), nil)
+	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
+	return
+}
diff --git a/openstack/loadbalancer/v2/quotas/results.go b/openstack/loadbalancer/v2/quotas/results.go
index e1ef38598..5487d7a84 100644
--- a/openstack/loadbalancer/v2/quotas/results.go
+++ b/openstack/loadbalancer/v2/quotas/results.go
@@ -96,3 +96,9 @@ func (r *Quota) UnmarshalJSON(b []byte) error {
 
 	return nil
 }
+
+// DeleteResult is the response from a Delete operation. Call its ExtractErr
+// method to determine if the request succeeded or failed.
+type DeleteResult struct {
+	gophercloud.ErrResult
+}
diff --git a/openstack/loadbalancer/v2/quotas/urls.go b/openstack/loadbalancer/v2/quotas/urls.go
index 0365d9c8a..545e83230 100644
--- a/openstack/loadbalancer/v2/quotas/urls.go
+++ b/openstack/loadbalancer/v2/quotas/urls.go
@@ -15,3 +15,7 @@ func getURL(c *gophercloud.ServiceClient, projectID string) string {
 func updateURL(c *gophercloud.ServiceClient, projectID string) string {
 	return resourceURL(c, projectID)
 }
+
+func deleteURL(c *gophercloud.ServiceClient, projectID string) string {
+	return getURL(c, projectID)
+}
diff --git a/openstack/networking/v2/extensions/quotas/doc.go b/openstack/networking/v2/extensions/quotas/doc.go
index fe1cc2631..5e5ec3e58 100644
--- a/openstack/networking/v2/extensions/quotas/doc.go
+++ b/openstack/networking/v2/extensions/quotas/doc.go
@@ -43,5 +43,15 @@ Example to Update project quotas
 	}
 
 	fmt.Printf("quotas: %#v\n", quotasInfo)
+
+Example to Delete project quotas
+
+	projectID = "23d5d3f79dfa4f73b72b8b0b0063ec55"
+	err := quotas.Delete(context.TODO(), networkClient, projectID).ExtractErr()
+	if err != nil {
+	    log.Fatal(err)
+	}
+
+	fmt.Printf("Deleted quotas for project: %s\n", projectID)
 */
 package quotas
diff --git a/openstack/networking/v2/extensions/quotas/requests.go b/openstack/networking/v2/extensions/quotas/requests.go
index 859c552ce..628936884 100644
--- a/openstack/networking/v2/extensions/quotas/requests.go
+++ b/openstack/networking/v2/extensions/quotas/requests.go
@@ -78,3 +78,9 @@ func Update(ctx context.Context, c *gophercloud.ServiceClient, projectID string,
 	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
 	return
 }
+
+func Delete(ctx context.Context, c *gophercloud.ServiceClient, projectID string) (r DeleteResult) {
+	resp, err := c.Delete(ctx, deleteURL(c, projectID), nil)
+	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
+	return
+}
diff --git a/openstack/networking/v2/extensions/quotas/results.go b/openstack/networking/v2/extensions/quotas/results.go
index 249d11517..5fa10cf21 100644
--- a/openstack/networking/v2/extensions/quotas/results.go
+++ b/openstack/networking/v2/extensions/quotas/results.go
@@ -171,3 +171,9 @@ func (q *QuotaDetail) UnmarshalJSON(b []byte) error {
 
 	return nil
 }
+
+// DeleteResult is the response from a Delete operation. Call its ExtractErr
+// method to determine if the request succeeded or failed.
+type DeleteResult struct {
+	gophercloud.ErrResult
+}
diff --git a/openstack/networking/v2/extensions/quotas/urls.go b/openstack/networking/v2/extensions/quotas/urls.go
index 94cbe2388..726c4da8e 100644
--- a/openstack/networking/v2/extensions/quotas/urls.go
+++ b/openstack/networking/v2/extensions/quotas/urls.go
@@ -24,3 +24,7 @@ func getDetailURL(c *gophercloud.ServiceClient, projectID string) string {
 func updateURL(c *gophercloud.ServiceClient, projectID string) string {
 	return resourceURL(c, projectID)
 }
+
+func deleteURL(c *gophercloud.ServiceClient, projectID string) string {
+	return getURL(c, projectID)
+}



Test patch:
diff --git a/internal/acceptance/openstack/loadbalancer/v2/quotas_test.go b/internal/acceptance/openstack/loadbalancer/v2/quotas_test.go
index cc778e540..87f9d7621 100644
--- a/internal/acceptance/openstack/loadbalancer/v2/quotas_test.go
+++ b/internal/acceptance/openstack/loadbalancer/v2/quotas_test.go
@@ -80,3 +80,13 @@ func TestQuotasUpdate(t *testing.T) {
 
 	tools.PrintResource(t, restoredQuotas)
 }
+
+func TestQuotasDelete(t *testing.T) {
+	clients.RequireAdmin(t)
+
+	client, err := clients.NewLoadBalancerV2Client()
+	th.AssertNoErr(t, err)
+
+	err = quotas.Delete(context.TODO(), client, os.Getenv("OS_PROJECT_NAME")).ExtractErr()
+	th.AssertNoErr(t, err)
+}
diff --git a/internal/acceptance/openstack/networking/v2/extensions/quotas/quotas_test.go b/internal/acceptance/openstack/networking/v2/extensions/quotas/quotas_test.go
index 3eaf7d232..06b228079 100644
--- a/internal/acceptance/openstack/networking/v2/extensions/quotas/quotas_test.go
+++ b/internal/acceptance/openstack/networking/v2/extensions/quotas/quotas_test.go
@@ -63,3 +63,13 @@ func TestQuotasUpdate(t *testing.T) {
 
 	tools.PrintResource(t, restoredQuotas)
 }
+
+func TestQuotasDelete(t *testing.T) {
+	clients.RequireAdmin(t)
+
+	client, err := clients.NewNetworkV2Client()
+	th.AssertNoErr(t, err)
+
+	err = quotas.Delete(context.TODO(), client, os.Getenv("OS_PROJECT_NAME")).ExtractErr()
+	th.AssertNoErr(t, err)
+}
diff --git a/openstack/loadbalancer/v2/quotas/testing/fixtures_test.go b/openstack/loadbalancer/v2/quotas/testing/fixtures_test.go
index e5185b201..e4609aa86 100644
--- a/openstack/loadbalancer/v2/quotas/testing/fixtures_test.go
+++ b/openstack/loadbalancer/v2/quotas/testing/fixtures_test.go
@@ -1,6 +1,13 @@
 package testing
 
-import "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/quotas"
+import (
+	"net/http"
+	"testing"
+
+	"github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/quotas"
+	fake "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/common"
+	th "github.com/gophercloud/gophercloud/v2/testhelper"
+)
 
 const GetResponseRaw_1 = `
 {
@@ -77,3 +84,12 @@ var UpdateResponse = quotas.Quota{
 	L7Policy:      50,
 	L7Rule:        100,
 }
+
+func MockDeleteResponse(t *testing.T, fakeServer th.FakeServer) {
+	fakeServer.Mux.HandleFunc("/v2.0/quotas/0a73845280574ad389c292f6a74afa76", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "DELETE")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+		w.WriteHeader(http.StatusNoContent)
+	})
+}
diff --git a/openstack/loadbalancer/v2/quotas/testing/requests_test.go b/openstack/loadbalancer/v2/quotas/testing/requests_test.go
index ccf8e51f3..ab3ba6a17 100644
--- a/openstack/loadbalancer/v2/quotas/testing/requests_test.go
+++ b/openstack/loadbalancer/v2/quotas/testing/requests_test.go
@@ -105,3 +105,13 @@ func TestUpdate_2(t *testing.T) {
 	th.AssertNoErr(t, err)
 	th.AssertDeepEquals(t, q, &UpdateResponse)
 }
+
+func TestDelete(t *testing.T) {
+	fakeServer := th.SetupHTTP()
+	defer fakeServer.Teardown()
+
+	MockDeleteResponse(t, fakeServer)
+
+	res := quotas.Delete(context.TODO(), fake.ServiceClient(fakeServer), "0a73845280574ad389c292f6a74afa76")
+	th.AssertNoErr(t, res.Err)
+}
diff --git a/openstack/networking/v2/extensions/quotas/testing/fixtures_test.go b/openstack/networking/v2/extensions/quotas/testing/fixtures_test.go
index a4a7703c8..1f34dead1 100644
--- a/openstack/networking/v2/extensions/quotas/testing/fixtures_test.go
+++ b/openstack/networking/v2/extensions/quotas/testing/fixtures_test.go
@@ -1,7 +1,12 @@
 package testing
 
 import (
+	"net/http"
+	"testing"
+
+	fake "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/common"
 	"github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/quotas"
+	th "github.com/gophercloud/gophercloud/v2/testhelper"
 )
 
 const GetResponseRaw = `
@@ -140,3 +145,12 @@ var UpdateResponse = quotas.Quota{
 	SubnetPool:        0,
 	Trunk:             5,
 }
+
+func MockDeleteResponse(t *testing.T, fakeServer th.FakeServer) {
+	fakeServer.Mux.HandleFunc("/v2.0/quotas/0a73845280574ad389c292f6a74afa76", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "DELETE")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+		w.WriteHeader(http.StatusNoContent)
+	})
+}
diff --git a/openstack/networking/v2/extensions/quotas/testing/requests_test.go b/openstack/networking/v2/extensions/quotas/testing/requests_test.go
index 597edd485..e4550c11f 100644
--- a/openstack/networking/v2/extensions/quotas/testing/requests_test.go
+++ b/openstack/networking/v2/extensions/quotas/testing/requests_test.go
@@ -80,3 +80,13 @@ func TestUpdate(t *testing.T) {
 	th.AssertNoErr(t, err)
 	th.AssertDeepEquals(t, q, &UpdateResponse)
 }
+
+func TestDelete(t *testing.T) {
+	fakeServer := th.SetupHTTP()
+	defer fakeServer.Teardown()
+
+	MockDeleteResponse(t, fakeServer)
+
+	res := quotas.Delete(context.TODO(), fake.ServiceClient(fakeServer), "0a73845280574ad389c292f6a74afa76")
+	th.AssertNoErr(t, res.Err)
+}