Repository: gophercloud/gophercloud

Issue:
Add support for placement resource provider trait in placement
Adding support for updating/deleting the placement resource provider traits  in Gophercloud.

There is no support for following APIs related to update resource provider traits.
https://docs.openstack.org/api-ref/placement/?expanded=update-resource-provider-traits-detail
https://docs.openstack.org/api-ref/placement/?expanded=update-resource-provider-traits-detail,delete-resource-provider-traits-detail

Discussion:

Solution patch:
diff --git a/openstack/placement/v1/resourceproviders/requests.go b/openstack/placement/v1/resourceproviders/requests.go
index 5c5075e44..f0dfa9d66 100644
--- a/openstack/placement/v1/resourceproviders/requests.go
+++ b/openstack/placement/v1/resourceproviders/requests.go
@@ -175,3 +175,38 @@ func GetTraits(ctx context.Context, client *gophercloud.ServiceClient, resourceP
 	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
 	return
 }
+
+// UpdateTraitsOptsBuilder allows extensions to add additional parameters to the
+// UpdateTraits request.
+type UpdateTraitsOptsBuilder interface {
+	ToResourceProviderUpdateTraitsMap() (map[string]any, error)
+}
+
+// UpdateTraitsOpts represents options used to update traits of a resource provider.
+type UpdateTraitsOpts = ResourceProviderTraits
+
+// ToResourceProviderUpdateTraitsMap constructs a request body from UpdateTraitsOpts.
+func (opts UpdateTraitsOpts) ToResourceProviderUpdateTraitsMap() (map[string]any, error) {
+	return gophercloud.BuildRequestBody(opts, "")
+}
+
+func UpdateTraits(ctx context.Context, client *gophercloud.ServiceClient, resourceProviderID string, opts UpdateTraitsOptsBuilder) (r GetTraitsResult) {
+	b, err := opts.ToResourceProviderUpdateTraitsMap()
+	if err != nil {
+		r.Err = err
+		return
+	}
+	resp, err := client.Put(ctx, getResourceProviderTraitsURL(client, resourceProviderID), b, &r.Body, &gophercloud.RequestOpts{
+		OkCodes: []int{200},
+	})
+	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
+	return
+}
+
+func DeleteTraits(ctx context.Context, client *gophercloud.ServiceClient, resourceProviderID string) (r DeleteResult) {
+	resp, err := client.Delete(ctx, getResourceProviderTraitsURL(client, resourceProviderID), &gophercloud.RequestOpts{
+		OkCodes: []int{204},
+	})
+	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
+	return
+}



Test patch:
diff --git a/openstack/placement/v1/resourceproviders/testing/fixtures_test.go b/openstack/placement/v1/resourceproviders/testing/fixtures_test.go
index 7ddc91b24..6ac4b8ca7 100644
--- a/openstack/placement/v1/resourceproviders/testing/fixtures_test.go
+++ b/openstack/placement/v1/resourceproviders/testing/fixtures_test.go
@@ -387,3 +387,29 @@ func HandleResourceProviderGetTraits(t *testing.T, fakeServer th.FakeServer) {
 			fmt.Fprint(w, TraitsBody)
 		})
 }
+
+func HandleResourceProviderPutTraits(t *testing.T, fakeServer th.FakeServer) {
+	traitsTestUrl := fmt.Sprintf("/resource_providers/%s/traits", ResourceProviderTestID)
+
+	fakeServer.Mux.HandleFunc(traitsTestUrl,
+		func(w http.ResponseWriter, r *http.Request) {
+			th.TestMethod(t, r, "PUT")
+			th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+
+			w.Header().Add("Content-Type", "application/json")
+			w.WriteHeader(http.StatusOK)
+
+			fmt.Fprint(w, TraitsBody)
+		})
+}
+
+func HandleResourceProviderDeleteTraits(t *testing.T, fakeServer th.FakeServer) {
+	traitsTestUrl := fmt.Sprintf("/resource_providers/%s/traits", ResourceProviderTestID)
+
+	fakeServer.Mux.HandleFunc(traitsTestUrl,
+		func(w http.ResponseWriter, r *http.Request) {
+			th.TestMethod(t, r, "DELETE")
+			th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+			w.WriteHeader(http.StatusNoContent)
+		})
+}
diff --git a/openstack/placement/v1/resourceproviders/testing/requests_test.go b/openstack/placement/v1/resourceproviders/testing/requests_test.go
index 1336126ec..4633c8113 100644
--- a/openstack/placement/v1/resourceproviders/testing/requests_test.go
+++ b/openstack/placement/v1/resourceproviders/testing/requests_test.go
@@ -145,3 +145,25 @@ func TestGetResourceProvidersTraits(t *testing.T) {
 	th.AssertNoErr(t, err)
 	th.AssertDeepEquals(t, ExpectedTraits, *actual)
 }
+
+func TestUpdateResourceProvidersTraits(t *testing.T) {
+	fakeServer := th.SetupHTTP()
+	defer fakeServer.Teardown()
+
+	HandleResourceProviderPutTraits(t, fakeServer)
+
+	opts := resourceproviders.UpdateTraitsOpts(ExpectedTraits)
+	actual, err := resourceproviders.UpdateTraits(context.TODO(), client.ServiceClient(fakeServer), ResourceProviderTestID, opts).Extract()
+	th.AssertNoErr(t, err)
+	th.AssertDeepEquals(t, ExpectedTraits, *actual)
+}
+
+func TestDeleteResourceProvidersTraits(t *testing.T) {
+	fakeServer := th.SetupHTTP()
+	defer fakeServer.Teardown()
+
+	HandleResourceProviderDeleteTraits(t, fakeServer)
+
+	err := resourceproviders.DeleteTraits(context.TODO(), client.ServiceClient(fakeServer), ResourceProviderTestID).ExtractErr()
+	th.AssertNoErr(t, err)
+}