Skip to content

Commit 58757da

Browse files
gkechhors
andauthored
K8SPSMDB-1640 configure globally read and write concerns (#2406)
* K8SPSMDB-1640 configure globally read and write concerns * fix for arbiter case --------- Co-authored-by: Viacheslav Sarzhan <slava.sarzhan@percona.com>
1 parent 5134449 commit 58757da

11 files changed

Lines changed: 263 additions & 4 deletions

File tree

config/crd/bases/psmdb.percona.com_perconaservermongodbs.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,19 @@ spec:
707707
type: string
708708
crVersion:
709709
type: string
710+
defaultRWConcern:
711+
properties:
712+
readConcern:
713+
enum:
714+
- local
715+
- available
716+
- majority
717+
- linearizable
718+
- snapshot
719+
type: string
720+
writeConcern:
721+
type: string
722+
type: object
710723
enableExternalVolumeAutoscaling:
711724
type: boolean
712725
enableVolumeExpansion:

deploy/bundle.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2733,6 +2733,19 @@ spec:
27332733
type: string
27342734
crVersion:
27352735
type: string
2736+
defaultRWConcern:
2737+
properties:
2738+
readConcern:
2739+
enum:
2740+
- local
2741+
- available
2742+
- majority
2743+
- linearizable
2744+
- snapshot
2745+
type: string
2746+
writeConcern:
2747+
type: string
2748+
type: object
27362749
enableExternalVolumeAutoscaling:
27372750
type: boolean
27382751
enableVolumeExpansion:

deploy/crd.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2733,6 +2733,19 @@ spec:
27332733
type: string
27342734
crVersion:
27352735
type: string
2736+
defaultRWConcern:
2737+
properties:
2738+
readConcern:
2739+
enum:
2740+
- local
2741+
- available
2742+
- majority
2743+
- linearizable
2744+
- snapshot
2745+
type: string
2746+
writeConcern:
2747+
type: string
2748+
type: object
27362749
enableExternalVolumeAutoscaling:
27372750
type: boolean
27382751
enableVolumeExpansion:

deploy/cw-bundle.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2733,6 +2733,19 @@ spec:
27332733
type: string
27342734
crVersion:
27352735
type: string
2736+
defaultRWConcern:
2737+
properties:
2738+
readConcern:
2739+
enum:
2740+
- local
2741+
- available
2742+
- majority
2743+
- linearizable
2744+
- snapshot
2745+
type: string
2746+
writeConcern:
2747+
type: string
2748+
type: object
27362749
enableExternalVolumeAutoscaling:
27372750
type: boolean
27382751
enableVolumeExpansion:

e2e-tests/version-service/conf/crd.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2733,6 +2733,19 @@ spec:
27332733
type: string
27342734
crVersion:
27352735
type: string
2736+
defaultRWConcern:
2737+
properties:
2738+
readConcern:
2739+
enum:
2740+
- local
2741+
- available
2742+
- majority
2743+
- linearizable
2744+
- snapshot
2745+
type: string
2746+
writeConcern:
2747+
type: string
2748+
type: object
27362749
enableExternalVolumeAutoscaling:
27372750
type: boolean
27382751
enableVolumeExpansion:

pkg/apis/psmdb/v1/psmdb_types.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,13 @@ type PerconaServerMongoDBSpec struct {
106106
StorageScaling *StorageScalingSpec `json:"storageScaling,omitempty"`
107107
VaultSpec *VaultSpec `json:"vault,omitempty"`
108108
Search *SearchSpec `json:"search,omitempty"`
109+
DefaultRWConcern *DefaultRWConcern `json:"defaultRWConcern,omitempty"`
110+
}
111+
112+
type DefaultRWConcern struct {
113+
// +kubebuilder:validation:Enum={local,available,majority,linearizable,snapshot}
114+
ReadConcern string `json:"readConcern,omitempty"`
115+
WriteConcern string `json:"writeConcern,omitempty"`
109116
}
110117

111118
type UserRole struct {

pkg/apis/psmdb/v1/zz_generated.deepcopy.go

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

pkg/controller/perconaservermongodb/mgo.go

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,28 @@ import (
3232

3333
var errReplsetLimit = fmt.Errorf("maximum replset member (%d) count reached", mongo.MaxMembers)
3434

35+
func defaultRWConcern(cr *api.PerconaServerMongoDB) (string, string) {
36+
readConcern, writeConcern := mongo.DefaultReadConcern, mongo.DefaultWriteConcern
37+
if c := cr.Spec.DefaultRWConcern; c != nil {
38+
if c.ReadConcern != "" {
39+
readConcern = c.ReadConcern
40+
}
41+
if c.WriteConcern != "" {
42+
writeConcern = c.WriteConcern
43+
}
44+
}
45+
return readConcern, writeConcern
46+
}
47+
48+
// PSA replsets need the explicit push since MongoDB's implicit default is w:1; user-set
49+
// values must also be propagated. Sharded clusters are handled via mongos.
50+
func shouldSetDefaultRWConcern(cr *api.PerconaServerMongoDB, replset *api.ReplsetSpec) bool {
51+
if cr.Spec.Sharding.Enabled {
52+
return false
53+
}
54+
return replset.Arbiter.Enabled || cr.Spec.DefaultRWConcern != nil
55+
}
56+
3557
func (r *ReconcilePerconaServerMongoDB) reconcileCluster(ctx context.Context, cr *api.PerconaServerMongoDB, replset *api.ReplsetSpec, mongosPods []corev1.Pod) (api.AppState, map[string]api.ReplsetMemberStatus, error) {
3658
log := logf.FromContext(ctx)
3759

@@ -205,7 +227,8 @@ func (r *ReconcilePerconaServerMongoDB) reconcileCluster(ctx context.Context, cr
205227
}
206228
}()
207229

208-
err = mongosSession.SetDefaultRWConcern(ctx, mongo.DefaultReadConcern, mongo.DefaultWriteConcern)
230+
readConcern, writeConcern := defaultRWConcern(cr)
231+
err = mongosSession.SetDefaultRWConcern(ctx, readConcern, writeConcern)
209232
// SetDefaultRWConcern introduced in MongoDB 4.4
210233
if err != nil && !strings.Contains(err.Error(), "CommandNotFound") {
211234
return api.AppStateError, nil, errors.Wrap(err, "set default RW concern")
@@ -239,8 +262,9 @@ func (r *ReconcilePerconaServerMongoDB) reconcileCluster(ctx context.Context, cr
239262
}
240263
}
241264

242-
if replset.Arbiter.Enabled && !cr.Spec.Sharding.Enabled {
243-
err := cli.SetDefaultRWConcern(ctx, mongo.DefaultReadConcern, mongo.DefaultWriteConcern)
265+
if shouldSetDefaultRWConcern(cr, replset) {
266+
readConcern, writeConcern := defaultRWConcern(cr)
267+
err := cli.SetDefaultRWConcern(ctx, readConcern, writeConcern)
244268
// SetDefaultRWConcern introduced in MongoDB 4.4
245269
if err != nil && !strings.Contains(err.Error(), "CommandNotFound") {
246270
return api.AppStateError, nil, errors.Wrap(err, "set default RW concern")

pkg/controller/perconaservermongodb/mgo_test.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,106 @@ func TestCompareTags(t *testing.T) {
5959
}
6060
}
6161

62+
func TestDefaultRWConcern(t *testing.T) {
63+
t.Parallel()
64+
65+
tests := map[string]struct {
66+
spec *api.DefaultRWConcern
67+
wantReadConcern string
68+
wantWriteConcern string
69+
}{
70+
"nil spec falls back to majority": {
71+
spec: nil,
72+
wantReadConcern: mongo.DefaultReadConcern,
73+
wantWriteConcern: mongo.DefaultWriteConcern,
74+
},
75+
"empty fields fall back to majority": {
76+
spec: &api.DefaultRWConcern{},
77+
wantReadConcern: mongo.DefaultReadConcern,
78+
wantWriteConcern: mongo.DefaultWriteConcern,
79+
},
80+
"only read overridden": {
81+
spec: &api.DefaultRWConcern{ReadConcern: "local"},
82+
wantReadConcern: "local",
83+
wantWriteConcern: mongo.DefaultWriteConcern,
84+
},
85+
"only write overridden": {
86+
spec: &api.DefaultRWConcern{WriteConcern: "1"},
87+
wantReadConcern: mongo.DefaultReadConcern,
88+
wantWriteConcern: "1",
89+
},
90+
"both overridden": {
91+
spec: &api.DefaultRWConcern{ReadConcern: "local", WriteConcern: "1"},
92+
wantReadConcern: "local",
93+
wantWriteConcern: "1",
94+
},
95+
}
96+
97+
for name, tt := range tests {
98+
t.Run(name, func(t *testing.T) {
99+
cr := &api.PerconaServerMongoDB{
100+
Spec: api.PerconaServerMongoDBSpec{DefaultRWConcern: tt.spec},
101+
}
102+
gotRead, gotWrite := defaultRWConcern(cr)
103+
assert.Equal(t, tt.wantReadConcern, gotRead)
104+
assert.Equal(t, tt.wantWriteConcern, gotWrite)
105+
})
106+
}
107+
}
108+
109+
func TestShouldSetDefaultRWConcern(t *testing.T) {
110+
t.Parallel()
111+
112+
tests := map[string]struct {
113+
shardingEnabled bool
114+
arbiterEnabled bool
115+
rwConcern *api.DefaultRWConcern
116+
want bool
117+
}{
118+
"PSS, no custom concern": {
119+
want: false,
120+
},
121+
"PSA, no custom concern": {
122+
arbiterEnabled: true,
123+
want: true,
124+
},
125+
"PSS, custom concern": {
126+
rwConcern: &api.DefaultRWConcern{WriteConcern: "1"},
127+
want: true,
128+
},
129+
"PSA, custom concern": {
130+
arbiterEnabled: true,
131+
rwConcern: &api.DefaultRWConcern{WriteConcern: "1"},
132+
want: true,
133+
},
134+
"sharded, PSA": {
135+
shardingEnabled: true,
136+
arbiterEnabled: true,
137+
want: false,
138+
},
139+
"sharded, custom concern": {
140+
shardingEnabled: true,
141+
rwConcern: &api.DefaultRWConcern{WriteConcern: "1"},
142+
want: false,
143+
},
144+
}
145+
146+
for name, tc := range tests {
147+
t.Run(name, func(t *testing.T) {
148+
cr := &api.PerconaServerMongoDB{
149+
Spec: api.PerconaServerMongoDBSpec{
150+
Sharding: api.Sharding{Enabled: tc.shardingEnabled},
151+
DefaultRWConcern: tc.rwConcern,
152+
},
153+
}
154+
rs := &api.ReplsetSpec{
155+
Arbiter: api.Arbiter{Enabled: tc.arbiterEnabled},
156+
}
157+
assert.Equal(t, tc.want, shouldSetDefaultRWConcern(cr, rs))
158+
})
159+
}
160+
}
161+
62162
func TestGetRoles(t *testing.T) {
63163
tests := map[string]struct {
64164
crVersion string

pkg/psmdb/mongo/concern_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package mongo
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestParseWriteConcernW(t *testing.T) {
10+
t.Parallel()
11+
12+
tests := map[string]struct {
13+
in string
14+
want interface{}
15+
}{
16+
"majority stays a string": {in: "majority", want: "majority"},
17+
"custom tag stays a string": {in: "myTag", want: "myTag"},
18+
"integer 0 becomes int": {in: "0", want: 0},
19+
"integer 1 becomes int": {in: "1", want: 1},
20+
"integer 3 becomes int": {in: "3", want: 3},
21+
"negative falls back to string": {in: "-1", want: "-1"},
22+
"empty stays a string": {in: "", want: ""},
23+
"numeric-looking tag with letters stays a string": {in: "1a", want: "1a"},
24+
}
25+
26+
for name, tt := range tests {
27+
t.Run(name, func(t *testing.T) {
28+
assert.Equal(t, tt.want, parseWriteConcernW(tt.in))
29+
})
30+
}
31+
}

0 commit comments

Comments
 (0)