Skip to content

Commit 5936ca1

Browse files
Merge pull request #615 from lmiccini/fix-proxy-sigterm-handling
Fix proxy sigterm handling in amqp-proxy, enable CMR and override terminationGracePeriodSeconds
2 parents 2a70e75 + 8462f54 commit 5936ca1

4 files changed

Lines changed: 100 additions & 1 deletion

File tree

apis/rabbitmq/v1beta1/rabbitmq_webhook.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,16 @@ func (spec *RabbitMqSpecCore) Default(isNew bool) {
140140
spec.QueueType = &queueType
141141
}
142142

143+
// Migrate terminationGracePeriodSeconds from old default (604800) to new (60).
144+
// The old value was inherited from rabbitmq-cluster-operator and causes pods
145+
// to get stuck in Terminating state for up to a week.
146+
oldGracePeriod := int64(604800)
147+
newGracePeriod := int64(60)
148+
if spec.TerminationGracePeriodSeconds != nil && *spec.TerminationGracePeriodSeconds == oldGracePeriod {
149+
spec.TerminationGracePeriodSeconds = &newGracePeriod
150+
rabbitmqlog.Info("Migrating terminationGracePeriodSeconds from old default 604800 to 60")
151+
}
152+
143153
// Force Mirrored → Quorum when upgrading to RabbitMQ 4.x+.
144154
// Mirrored queues are not supported in 4.x, so the migration is mandatory.
145155
if spec.QueueType != nil && *spec.QueueType == QueueTypeMirrored &&
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
Copyright 2025.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1beta1
18+
19+
import "testing"
20+
21+
func TestSpecCoreDefault_MigratesOldGracePeriod(t *testing.T) {
22+
oldDefault := int64(604800)
23+
spec := RabbitMqSpecCore{
24+
TerminationGracePeriodSeconds: &oldDefault,
25+
}
26+
27+
spec.Default(false)
28+
29+
if spec.TerminationGracePeriodSeconds == nil {
30+
t.Fatal("TerminationGracePeriodSeconds should not be nil")
31+
}
32+
if *spec.TerminationGracePeriodSeconds != 60 {
33+
t.Errorf("TerminationGracePeriodSeconds = %d, want 60", *spec.TerminationGracePeriodSeconds)
34+
}
35+
}
36+
37+
func TestSpecCoreDefault_PreservesCustomGracePeriod(t *testing.T) {
38+
custom := int64(120)
39+
spec := RabbitMqSpecCore{
40+
TerminationGracePeriodSeconds: &custom,
41+
}
42+
43+
spec.Default(false)
44+
45+
if spec.TerminationGracePeriodSeconds == nil {
46+
t.Fatal("TerminationGracePeriodSeconds should not be nil")
47+
}
48+
if *spec.TerminationGracePeriodSeconds != 120 {
49+
t.Errorf("TerminationGracePeriodSeconds = %d, want 120", *spec.TerminationGracePeriodSeconds)
50+
}
51+
}
52+
53+
func TestSpecCoreDefault_PreservesNewDefault(t *testing.T) {
54+
newDefault := int64(60)
55+
spec := RabbitMqSpecCore{
56+
TerminationGracePeriodSeconds: &newDefault,
57+
}
58+
59+
spec.Default(false)
60+
61+
if spec.TerminationGracePeriodSeconds == nil {
62+
t.Fatal("TerminationGracePeriodSeconds should not be nil")
63+
}
64+
if *spec.TerminationGracePeriodSeconds != 60 {
65+
t.Errorf("TerminationGracePeriodSeconds = %d, want 60", *spec.TerminationGracePeriodSeconds)
66+
}
67+
}

internal/controller/rabbitmq/data/proxy.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import asyncio
1919
import argparse
2020
import logging
21+
import signal
2122
import struct
2223
import sys
2324
import ssl
@@ -909,14 +910,21 @@ async def main():
909910
# Start periodic stats
910911
stats_task = asyncio.create_task(periodic_stats(proxy, args.stats_interval))
911912

913+
# Handle SIGTERM (sent by kubelet) the same as SIGINT so the proxy
914+
# shuts down gracefully instead of hanging until the
915+
# terminationGracePeriodSeconds expires.
916+
loop = asyncio.get_running_loop()
917+
loop.add_signal_handler(signal.SIGTERM, server.close)
918+
912919
try:
913920
async with server:
914921
await server.serve_forever()
915-
except KeyboardInterrupt:
922+
except (KeyboardInterrupt, asyncio.CancelledError):
916923
logger.info("Shutting down...")
917924
finally:
918925
stats_task.cancel()
919926
proxy.print_stats()
927+
logger.info("Proxy stopped")
920928

921929

922930
if __name__ == '__main__':

internal/rabbitmq/configmap.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,20 @@ func buildOperatorDefaults(r *rabbitmqv1.RabbitMq, IPv6Enabled bool, configVersi
137137
)
138138
}
139139

140+
// Enable Continuous Membership Reconciliation (CMR) for multi-node clusters
141+
// on RabbitMQ 4.x. CMR automatically grows quorum queues onto nodes that are
142+
// missing from the membership, replacing the need for manual
143+
// `rabbitmq-queues grow` calls after upgrades or rolling restarts.
144+
// trigger_interval (10s) fires when a node joins, covering rolling restarts.
145+
// interval (60min default) is a background safety net.
146+
if IsVersion4OrLater(configVersion) && getReplicaCount(r) > 1 {
147+
config = append(config,
148+
"quorum_queue.continuous_membership_reconciliation.enabled = true",
149+
fmt.Sprintf("quorum_queue.continuous_membership_reconciliation.target_group_size = %d", getReplicaCount(r)),
150+
"quorum_queue.continuous_membership_reconciliation.auto_remove = false",
151+
)
152+
}
153+
140154
// Prometheus and management bind address
141155
config = append(config, "prometheus.tcp.ip = ::")
142156
config = append(config, "management.tcp.ip = ::")

0 commit comments

Comments
 (0)