Skip to content

Commit 9f86bfd

Browse files
committed
Delete existing limits if they are removed from a user.
1 parent 9d45712 commit 9f86bfd

File tree

5 files changed

+192
-5
lines changed

5 files changed

+192
-5
lines changed

controllers/user_controller.go

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"errors"
1515
"fmt"
1616

17+
rabbithole "github.com/michaelklishin/rabbit-hole/v2"
1718
topology "github.com/rabbitmq/messaging-topology-operator/api/v1beta1"
1819
"github.com/rabbitmq/messaging-topology-operator/internal"
1920
"github.com/rabbitmq/messaging-topology-operator/rabbitmqclient"
@@ -226,12 +227,46 @@ func (r *UserReconciler) DeclareFunc(ctx context.Context, client rabbitmqclient.
226227
return err
227228
}
228229

229-
userLimits := internal.GenerateUserLimits(user.Spec.UserLimits)
230-
logger.Info("Generated user limits", "user", user.Name, "limits", userLimits)
231-
if len(userLimits) > 0 {
232-
return validateResponse(client.PutUserLimits(string(credentials.Data["username"]), userLimits))
230+
newUserLimits := internal.GenerateUserLimits(user.Spec.UserLimits)
231+
existingUserLimits, err := r.getUserLimits(client, string(credentials.Data["username"]))
232+
if err != nil {
233+
return err
233234
}
234-
return nil
235+
logger.Info("Updating user limits", "user", user.Name, "existing limits", existingUserLimits, "new limits", newUserLimits)
236+
limitsToDelete := r.userLimitsToDelete(existingUserLimits, newUserLimits)
237+
if len(limitsToDelete) > 0 {
238+
err = validateResponseForDeletion(client.DeleteUserLimits(string(credentials.Data["username"]), limitsToDelete))
239+
if err != nil && !errors.Is(err, NotFound) {
240+
return err
241+
}
242+
logger.Info("Deleted user limits", "user", user.Name, "limits", limitsToDelete)
243+
}
244+
return validateResponse(client.PutUserLimits(string(credentials.Data["username"]), newUserLimits))
245+
}
246+
247+
func (r *UserReconciler) userLimitsToDelete(existingUserLimits, newUserLimits rabbithole.UserLimitsValues) (limitsToDelete rabbithole.UserLimits) {
248+
userLimitKeys := []string{"max-connections", "max-channels"}
249+
for _, limit := range userLimitKeys {
250+
_, oldExists := existingUserLimits[limit]
251+
_, newExists := newUserLimits[limit]
252+
if oldExists && !newExists {
253+
limitsToDelete = append(limitsToDelete, limit)
254+
}
255+
}
256+
return limitsToDelete
257+
}
258+
259+
func (r *UserReconciler) getUserLimits(client rabbitmqclient.Client, username string) (rabbithole.UserLimitsValues, error) {
260+
userLimitsInfo, err := client.GetUserLimits(username)
261+
if errors.Is(err, error(rabbithole404)) {
262+
return rabbithole.UserLimitsValues{}, nil
263+
} else if err != nil {
264+
return rabbithole.UserLimitsValues{}, err
265+
}
266+
if len(userLimitsInfo) == 0 {
267+
return rabbithole.UserLimitsValues{}, nil
268+
}
269+
return userLimitsInfo[0].Value, nil
235270
}
236271

237272
func (r *UserReconciler) getUserCredentials(ctx context.Context, user *topology.User) (*corev1.Secret, error) {

controllers/user_controller_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"sigs.k8s.io/controller-runtime/pkg/config"
1616
"sigs.k8s.io/controller-runtime/pkg/metrics/server"
1717

18+
rabbithole "github.com/michaelklishin/rabbit-hole/v2"
1819
. "github.com/onsi/ginkgo/v2"
1920
. "github.com/onsi/gomega"
2021
. "github.com/onsi/gomega/gstruct"
@@ -176,6 +177,11 @@ var _ = Describe("UserController", func() {
176177
Status: "201 Created",
177178
StatusCode: http.StatusCreated,
178179
}, nil)
180+
fakeRabbitMQClient.GetUserLimitsReturns(nil, rabbithole.ErrorResponse{
181+
StatusCode: 404,
182+
Message: "Object Not Found",
183+
Reason: "Not Found",
184+
})
179185
})
180186

181187
It("should create the user limits", func() {
@@ -203,6 +209,64 @@ var _ = Describe("UserController", func() {
203209
Expect(userLimitsValues).To(HaveKeyWithValue("max-channels", (int(channels))))
204210
})
205211
})
212+
213+
When("the user already has existing limits that differ from the new limits", func() {
214+
BeforeEach(func() {
215+
userName = "test-changed-user-limits"
216+
connections = 5
217+
userLimits = topology.UserLimits{
218+
Connections: &connections,
219+
Channels: nil,
220+
}
221+
var userLimitsInfo []rabbithole.UserLimitsInfo
222+
userLimitsInfo = append(userLimitsInfo, rabbithole.UserLimitsInfo{
223+
User: userName,
224+
Value: rabbithole.UserLimitsValues{"max-channels": 10, "max-connections": 3},
225+
})
226+
fakeRabbitMQClient.PutUserReturns(&http.Response{
227+
Status: "201 Created",
228+
StatusCode: http.StatusCreated,
229+
}, nil)
230+
fakeRabbitMQClient.PutUserLimitsReturns(&http.Response{
231+
Status: "201 Created",
232+
StatusCode: http.StatusCreated,
233+
}, nil)
234+
fakeRabbitMQClient.GetUserLimitsReturns(userLimitsInfo, nil)
235+
fakeRabbitMQClient.DeleteUserLimitsReturns(&http.Response{
236+
Status: "204 No Content",
237+
StatusCode: http.StatusNoContent,
238+
}, nil)
239+
})
240+
241+
It("should update the existing user limit and delete the unused old limit", func() {
242+
Expect(k8sClient.Create(ctx, &user)).To(Succeed())
243+
Eventually(func() []topology.Condition {
244+
_ = k8sClient.Get(
245+
ctx,
246+
types.NamespacedName{Name: user.Name, Namespace: user.Namespace},
247+
&user,
248+
)
249+
250+
return user.Status.Conditions
251+
}).
252+
Within(statusEventsUpdateTimeout).
253+
WithPolling(time.Second).
254+
Should(ContainElement(MatchFields(IgnoreExtras, Fields{
255+
"Type": Equal(topology.ConditionType("Ready")),
256+
"Reason": Equal("SuccessfulCreateOrUpdate"),
257+
"Status": Equal(corev1.ConditionTrue),
258+
})))
259+
By("calling DeleteUserLimits with the unused old user limits")
260+
Expect(fakeRabbitMQClient.DeleteUserLimitsCallCount()).To(BeNumerically(">", 0))
261+
_, userLimits := fakeRabbitMQClient.DeleteUserLimitsArgsForCall(0)
262+
Expect(userLimits).To(HaveLen(1))
263+
Expect(userLimits).To(ContainElement("max-channels"))
264+
By("calling PutUserLimits with the correct new user limits")
265+
Expect(fakeRabbitMQClient.PutUserLimitsCallCount()).To(BeNumerically(">", 0))
266+
_, userLimitsValues := fakeRabbitMQClient.PutUserLimitsArgsForCall(0)
267+
Expect(userLimitsValues).To(HaveKeyWithValue("max-connections", int(connections)))
268+
})
269+
})
206270
})
207271

208272
When("deleting a user", func() {

controllers/utils.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"strings"
1919
"time"
2020

21+
rabbithole "github.com/michaelklishin/rabbit-hole/v2"
2122
topology "github.com/rabbitmq/messaging-topology-operator/api/v1beta1"
2223
"github.com/rabbitmq/messaging-topology-operator/rabbitmqclient"
2324
"k8s.io/client-go/tools/record"
@@ -30,6 +31,13 @@ import (
3031
corev1 "k8s.io/api/core/v1"
3132
)
3233

34+
// returned in some cases as an error when rabbithole encounters a 404 response
35+
var rabbithole404 = rabbithole.ErrorResponse{
36+
StatusCode: 404,
37+
Message: "Object Not Found",
38+
Reason: "Not Found",
39+
}
40+
3341
// TODO: check possible status code response from RabbitMQ
3442
// validate status code above 300 might not be all failure case
3543
func validateResponse(res *http.Response, err error) error {

rabbitmqclient/rabbitmq_client_factory.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type Client interface {
2323
PutUser(string, rabbithole.UserSettings) (*http.Response, error)
2424
DeleteUser(string) (*http.Response, error)
2525
PutUserLimits(string, rabbithole.UserLimitsValues) (*http.Response, error)
26+
GetUserLimits(string) ([]rabbithole.UserLimitsInfo, error)
2627
DeleteUserLimits(string, rabbithole.UserLimits) (*http.Response, error)
2728
DeclareBinding(string, rabbithole.BindingInfo) (*http.Response, error)
2829
DeleteBinding(string, rabbithole.BindingInfo) (*http.Response, error)

rabbitmqclient/rabbitmqclientfakes/fake_client.go

Lines changed: 79 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)