Skip to content
This repository was archived by the owner on Jul 11, 2023. It is now read-only.

Commit c55b7db

Browse files
jaelliokeithmattix
andauthored
[backport] Add more robust CRD conversion patching (#5303)
Watch CRDs over a span of 15s to make sure that the CRD conversion patch is not overwritten by another reconciler Signed-off-by: Keith Mattix II <[email protected]> Co-authored-by: Keith Mattix II <[email protected]>
1 parent 00fd7e3 commit c55b7db

File tree

4 files changed

+120
-9
lines changed

4 files changed

+120
-9
lines changed

cmd/osm-bootstrap/osm-bootstrap.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"net/http"
1313
"os"
1414
"path/filepath"
15-
"strconv"
1615

1716
"github.com/spf13/pflag"
1817
admissionv1 "k8s.io/api/admissionregistration/v1"
@@ -248,7 +247,7 @@ func applyOrUpdateCRDs(crdClient *apiclient.ApiextensionsV1Client) {
248247
log.Fatal().Err(err).Msgf("Error decoding CRD file %s", file)
249248
}
250249

251-
crd.Labels[constants.ReconcileLabel] = strconv.FormatBool(enableReconciler)
250+
crd.Labels[constants.ReconcileLabel] = osmVersion
252251

253252
crdExisting, err := crdClient.CustomResourceDefinitions().Get(context.Background(), crd.Name, metav1.GetOptions{})
254253
if err != nil && !apierrors.IsNotFound(err) {
@@ -267,7 +266,7 @@ func applyOrUpdateCRDs(crdClient *apiclient.ApiextensionsV1Client) {
267266
} else {
268267
log.Info().Msgf("Patching conversion webhook configuration for crd: %s, setting to \"None\"", crd.Name)
269268

270-
crdExisting.Labels[constants.ReconcileLabel] = strconv.FormatBool(enableReconciler)
269+
crdExisting.Labels[constants.ReconcileLabel] = osmVersion
271270
crdExisting.Spec = crd.Spec
272271
crdExisting.Spec.Conversion = &apiv1.CustomResourceConversion{
273272
Strategy: apiv1.NoneConverter,

dockerfiles/Dockerfile.init

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
FROM alpine:3.17.3
2-
RUN apk add --no-cache iptables
2+
RUN apk add --no-cache iptables

pkg/reconciler/client.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ func NewReconcilerClient(kubeClient kubernetes.Interface, apiServerClient client
6060

6161
// Initializes CustomResourceDefinition monitoring
6262
func (c *client) initCustomResourceDefinitionMonitor() {
63-
osmCrdsLabel := map[string]string{constants.OSMAppNameLabelKey: constants.OSMAppNameLabelValue, constants.ReconcileLabel: strconv.FormatBool(true)}
63+
// Use the OSM version as the selector for reconciliation
64+
osmCrdsLabel := map[string]string{constants.OSMAppNameLabelKey: constants.OSMAppNameLabelValue, constants.ReconcileLabel: c.osmVersion}
6465

6566
labelSelector := fields.SelectorFromSet(osmCrdsLabel).String()
6667
options := internalinterfaces.TweakListOptionsFunc(func(opt *metav1.ListOptions) {

tests/e2e/e2e_reconciler_test.go

+115-4
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,24 @@ package e2e
22

33
import (
44
"context"
5+
"os"
6+
"path/filepath"
7+
"strconv"
58
"time"
69

710
. "github.com/onsi/ginkgo"
811
. "github.com/onsi/gomega"
9-
12+
apiv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
13+
"k8s.io/apimachinery/pkg/api/errors"
1014
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11-
15+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
16+
"k8s.io/apimachinery/pkg/runtime"
17+
"k8s.io/apimachinery/pkg/runtime/serializer"
18+
"k8s.io/kubectl/pkg/util"
19+
"k8s.io/utils/pointer"
20+
21+
"github.com/openservicemesh/osm/pkg/constants"
22+
"github.com/openservicemesh/osm/pkg/reconciler"
1223
. "github.com/openservicemesh/osm/tests/framework"
1324
)
1425

@@ -106,7 +117,7 @@ var _ = OSMDescribe("Test OSM Reconciler",
106117
Eventually(func() (string, error) {
107118
updatedVwhc, err := Td.Client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(context.Background(), "osm-validator-mesh-osm", metav1.GetOptions{})
108119
return updatedVwhc.Webhooks[0].ClientConfig.Service.Name, err
109-
}, 3*time.Second).Should(Equal(originalWebhookServiceName))
120+
}, 30*time.Second).Should(Equal(originalWebhookServiceName))
110121

111122
// delete the validating webhook
112123
err = Td.Client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Delete(context.Background(), "osm-validator-mesh-osm", metav1.DeleteOptions{})
@@ -116,7 +127,107 @@ var _ = OSMDescribe("Test OSM Reconciler",
116127
Eventually(func() error {
117128
_, err = Td.Client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(context.Background(), "osm-validator-mesh-osm", metav1.GetOptions{})
118129
return err
119-
}, 3*time.Second).Should(BeNil())
130+
}, 30*time.Second).Should(BeNil())
131+
})
132+
})
133+
134+
Context("Pre-existing reconciler with CRD conversion webhook change", func() {
135+
It("Retries until CRD change is detected 3 times, waiting for 5 seconds each time", func() {
136+
// Install CRDs with a conversion webhook
137+
crdFiles, err := filepath.Glob("../../cmd/osm-bootstrap/crds/*.yaml")
138+
139+
Expect(err).NotTo(HaveOccurred())
140+
141+
scheme := runtime.NewScheme()
142+
codecs := serializer.NewCodecFactory(scheme)
143+
decode := codecs.UniversalDeserializer().Decode
144+
145+
for _, file := range crdFiles {
146+
yaml, err := os.ReadFile(filepath.Clean(file))
147+
if err != nil {
148+
Expect(err).NotTo(HaveOccurred())
149+
}
150+
151+
crd := &apiv1.CustomResourceDefinition{}
152+
_, _, err = decode(yaml, nil, crd)
153+
if err != nil {
154+
Expect(err).NotTo(HaveOccurred())
155+
}
156+
157+
// imitate older version of OSM who set the reconcile label to a (stringified) bool
158+
crd.Labels[constants.ReconcileLabel] = strconv.FormatBool(true)
159+
160+
crd.Spec.Conversion = &apiv1.CustomResourceConversion{
161+
Strategy: apiv1.WebhookConverter,
162+
Webhook: &apiv1.WebhookConversion{
163+
ClientConfig: &apiv1.WebhookClientConfig{
164+
Service: &apiv1.ServiceReference{
165+
Namespace: "osm-system",
166+
Name: "osm-bootstrap",
167+
Path: pointer.StringPtr("/convert"),
168+
Port: pointer.Int32Ptr(9443),
169+
},
170+
},
171+
ConversionReviewVersions: []string{"v1alpha1", "v1alpha2", "v1alpha3", "v1beta1", "v1"},
172+
},
173+
}
174+
175+
err = util.CreateApplyAnnotation(crd, unstructured.UnstructuredJSONScheme)
176+
Expect(err).NotTo(HaveOccurred())
177+
178+
_, err = Td.APIServerClient.ApiextensionsV1().CustomResourceDefinitions().Create(context.Background(), crd, metav1.CreateOptions{})
179+
if errors.IsAlreadyExists(err) {
180+
existingCRD, err := Td.APIServerClient.ApiextensionsV1().CustomResourceDefinitions().Get(context.Background(), "meshconfigs.config.openservicemesh.io", metav1.GetOptions{})
181+
Expect(err).NotTo(HaveOccurred())
182+
existingCRD.Spec.Conversion = crd.Spec.Conversion
183+
existingCRD.Labels[constants.ReconcileLabel] = strconv.FormatBool(true)
184+
_, err = Td.APIServerClient.ApiextensionsV1().CustomResourceDefinitions().Update(context.Background(), existingCRD, metav1.UpdateOptions{})
185+
Expect(err).NotTo(HaveOccurred())
186+
} else {
187+
Expect(err).NotTo(HaveOccurred())
188+
}
189+
}
190+
// Add existing reconciler to watch the cluster
191+
_, cancel := context.WithCancel(context.Background())
192+
defer cancel()
193+
stop := make(chan struct{})
194+
Expect(err).NotTo(HaveOccurred())
195+
196+
// The current reconciler code matches the version to the label value, which is a stringified bool
197+
err = reconciler.NewReconcilerClient(Td.Client, Td.APIServerClient, "osm", "true", stop, reconciler.CrdInformerKey)
198+
Expect(err).NotTo(HaveOccurred())
199+
200+
// Confirm that the CRD conversion webhook is set to WebhookConverter
201+
crd, err := Td.APIServerClient.ApiextensionsV1().CustomResourceDefinitions().Get(context.Background(), "meshconfigs.config.openservicemesh.io", metav1.GetOptions{})
202+
Expect(err).NotTo(HaveOccurred())
203+
Expect(crd.Spec.Conversion.Strategy).To(Equal(apiv1.WebhookConverter))
204+
205+
// Install OSM with reconciler enabled
206+
installOpts := Td.GetOSMInstallOpts()
207+
installOpts.EnableReconciler = true
208+
Expect(Td.InstallOSM(installOpts)).To(Succeed())
209+
210+
Eventually(func() int {
211+
// Make sure the CRD conversion webhook is set to NoneConverter 3 times in a row
212+
var attempts int
213+
for i := 0; i < 3; attempts++ {
214+
if attempts >= 30 { // Gomega doesn't stop the function at the timeout, so prevent an infinite loop
215+
break
216+
}
217+
crd, err := Td.APIServerClient.ApiextensionsV1().CustomResourceDefinitions().Get(context.Background(), "meshconfigs.config.openservicemesh.io", metav1.GetOptions{})
218+
Expect(err).NotTo(HaveOccurred())
219+
if crd.Spec.Conversion.Strategy == apiv1.NoneConverter {
220+
i++
221+
} else {
222+
i = 0
223+
}
224+
time.Sleep(500 * time.Millisecond)
225+
}
226+
return attempts
227+
}, 15*time.Second).Should(BeNumerically("<", 30))
228+
229+
// Stop the first reconciler (simulate the OSM upgrade completion)
230+
close(stop)
120231
})
121232
})
122233
})

0 commit comments

Comments
 (0)