Skip to content

Commit c1120ec

Browse files
authored
fix(pvc): fix compatible issue if SC is changed (#6512)
1 parent f783ef4 commit c1120ec

4 files changed

Lines changed: 107 additions & 14 deletions

File tree

pkg/client/client.go

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ const (
5050

5151
type Client interface {
5252
client.WithWatch
53-
Apply(ctx context.Context, obj client.Object) error
54-
ApplyWithResult(ctx context.Context, obj client.Object) (ApplyResult, error)
53+
Apply(ctx context.Context, obj client.Object, opts ...ApplyOption) error
54+
ApplyWithResult(ctx context.Context, obj client.Object, opts ...ApplyOption) (ApplyResult, error)
5555
}
5656

5757
type ApplyResult int
@@ -80,9 +80,9 @@ type applier struct {
8080
parser GVKParser
8181
}
8282

83-
func (p *applier) Apply(ctx context.Context, obj client.Object) error {
83+
func (p *applier) Apply(ctx context.Context, obj client.Object, opts ...ApplyOption) error {
8484
logger := logr.FromContextOrDiscard(ctx)
85-
res, err := p.ApplyWithResult(ctx, obj)
85+
res, err := p.ApplyWithResult(ctx, obj, opts...)
8686
if err == nil {
8787
logger.Info("apply success",
8888
"kind", reflect.TypeOf(obj),
@@ -94,7 +94,12 @@ func (p *applier) Apply(ctx context.Context, obj client.Object) error {
9494
return err
9595
}
9696

97-
func (p *applier) ApplyWithResult(ctx context.Context, obj client.Object) (ApplyResult, error) {
97+
func (p *applier) ApplyWithResult(ctx context.Context, obj client.Object, opts ...ApplyOption) (ApplyResult, error) {
98+
o := &ApplyOptions{}
99+
for _, opt := range opts {
100+
opt.With(o)
101+
}
102+
98103
gvks, _, err := scheme.Scheme.ObjectKinds(obj)
99104
if err != nil {
100105
return ApplyResultUnchanged, fmt.Errorf("cannot get gvks of the obj %T: %w", obj, err)
@@ -122,6 +127,19 @@ func (p *applier) ApplyWithResult(ctx context.Context, obj client.Object) (Apply
122127
if err != nil {
123128
return ApplyResultUnchanged, err
124129
}
130+
for _, fields := range o.Immutable {
131+
val, exists, err := unstructured.NestedFieldNoCopy(current.Object, fields...)
132+
if err != nil {
133+
return ApplyResultUnchanged, fmt.Errorf("cannot get immutable fields %v: %w", fields, err)
134+
}
135+
if !exists {
136+
unstructured.RemoveNestedField(expected.Object, fields...)
137+
} else {
138+
if err := unstructured.SetNestedField(expected.Object, val, fields...); err != nil {
139+
return ApplyResultUnchanged, fmt.Errorf("cannot set immutable fields %v: %w", fields, err)
140+
}
141+
}
142+
}
125143
lastApplied, err := p.Extract(current, DefaultFieldManager, gvks[0], "")
126144
if err != nil {
127145
return ApplyResultUnchanged, fmt.Errorf("cannot extract last applied patch: %w", err)

pkg/client/client_test.go

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,13 @@ import (
2828

2929
func TestApply(t *testing.T) {
3030
cases := []struct {
31-
desc string
32-
objs []client.Object
33-
obj client.Object
34-
expected client.Object
35-
res ApplyResult
36-
hasErr bool
31+
desc string
32+
objs []client.Object
33+
obj client.Object
34+
expected client.Object
35+
res ApplyResult
36+
immutable []string
37+
hasErr bool
3738
}{
3839
{
3940
desc: "apply a new obj",
@@ -62,6 +63,26 @@ func TestApply(t *testing.T) {
6263
expected: fake.FakeObj("aa", fake.GVK[corev1.Pod](corev1.SchemeGroupVersion), fake.Label[corev1.Pod]("test", "test")),
6364
res: ApplyResultUnchanged,
6465
},
66+
{
67+
desc: "apply for an existing obj with immutable fields",
68+
objs: []client.Object{
69+
fake.FakeObj("aa", fake.Label[corev1.Pod]("test", "test"), func(obj *corev1.Pod) *corev1.Pod {
70+
obj.Spec.NodeName = "xxx"
71+
return obj
72+
}),
73+
},
74+
obj: fake.FakeObj("aa", fake.Label[corev1.Pod]("test", "test"), func(obj *corev1.Pod) *corev1.Pod {
75+
// nodeName is immutable
76+
obj.Spec.NodeName = "yyy"
77+
return obj
78+
}),
79+
expected: fake.FakeObj("aa", fake.GVK[corev1.Pod](corev1.SchemeGroupVersion), fake.Label[corev1.Pod]("test", "test"), func(obj *corev1.Pod) *corev1.Pod {
80+
obj.Spec.NodeName = "xxx"
81+
return obj
82+
}),
83+
immutable: []string{"spec", "nodeName"},
84+
res: ApplyResultUnchanged,
85+
},
6586
}
6687

6788
for i := range cases {
@@ -71,10 +92,10 @@ func TestApply(t *testing.T) {
7192

7293
p := NewFakeClient()
7394
for _, obj := range c.objs {
74-
err := p.Apply(context.TODO(), obj)
95+
err := p.Apply(context.TODO(), obj, Immutable(c.immutable...))
7596
require.NoError(tt, err)
7697
}
77-
res, err := p.ApplyWithResult(context.TODO(), c.obj)
98+
res, err := p.ApplyWithResult(context.TODO(), c.obj, Immutable(c.immutable...))
7899
if c.hasErr {
79100
assert.Error(tt, err)
80101
} else {

pkg/client/opt.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2024 PingCAP, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package client
16+
17+
type ApplyOptions struct {
18+
// Immutable defines fields which is immutable
19+
// It's only for some fields which cannot be changed but actually maybe changed.
20+
// For example,
21+
// Storage class ref can be changed in instance CR to modify volumes
22+
// if feature VolumeAttributesClass is not enabled(just like v1).
23+
// However, it's immutable in PVC. When VolumeAttributesClass is enabled,
24+
// storage class should not be applied to PVC.
25+
// NOTE; now slice/array is not supported
26+
Immutable [][]string
27+
}
28+
29+
type ApplyOption interface {
30+
With(opts *ApplyOptions)
31+
}
32+
33+
type ApplyOptionFunc func(opts *ApplyOptions)
34+
35+
func (f ApplyOptionFunc) With(opts *ApplyOptions) {
36+
f(opts)
37+
}
38+
39+
func Immutable(fields ...string) ApplyOption {
40+
return ApplyOptionFunc(func(opts *ApplyOptions) {
41+
if len(fields) == 0 {
42+
return
43+
}
44+
opts.Immutable = append(opts.Immutable, fields)
45+
})
46+
}

pkg/volumes/sync.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,15 @@ func SyncPVCs(ctx context.Context, c client.Client, pvcs []*corev1.PersistentVol
105105
}
106106

107107
func syncPVC(ctx context.Context, c client.Client, pvc *corev1.PersistentVolumeClaim) error {
108-
if err := c.Apply(ctx, pvc); err != nil {
108+
if err := c.Apply(
109+
ctx,
110+
pvc,
111+
// If VAC is not enabled, we use params in SC to modify volume.
112+
// So we allow SC ref being changed.
113+
// If VAC is enabled, we should also not apply changed SC to PVC.
114+
// Reset to SC of current PVC.
115+
client.Immutable("spec", "storageClassName"),
116+
); err != nil {
109117
return err
110118
}
111119
msgs := isPVCSynced(pvc)

0 commit comments

Comments
 (0)