Skip to content

Commit d7971b7

Browse files
authored
RHOAIENG-25707: test(odh-nbc): additional testcases as per review feedback (#643)
* RHOAIENG-13916: refactor(tests): enhance Notebook webhook test cases to utilize helper functions for resource creation * RHOAIENG-23765: enhance(tests): expand test coverage for Notebook webhook image resolution logic, including namespace-specific and multi-ImageStream scenarios * NO-JIRA: chore(tests): fix import formatting in `notebook_webhook_test.go`
1 parent b1945d4 commit d7971b7

File tree

1 file changed

+148
-80
lines changed

1 file changed

+148
-80
lines changed

components/odh-notebook-controller/controllers/notebook_webhook_test.go

Lines changed: 148 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
corev1 "k8s.io/api/core/v1"
2626
apierrs "k8s.io/apimachinery/pkg/api/errors"
2727
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28+
2829
"sigs.k8s.io/controller-runtime/pkg/client"
2930

3031
. "github.com/onsi/ginkgo"
@@ -48,20 +49,70 @@ var _ = Describe("The Openshift Notebook webhook", func() {
4849
}
4950
})
5051

52+
var newImageStream = func(name, namespace, tag, dockerImageReference string) *imagev1.ImageStream {
53+
return &imagev1.ImageStream{
54+
TypeMeta: metav1.TypeMeta{
55+
Kind: "ImageStream",
56+
APIVersion: "image.openshift.io/v1",
57+
},
58+
ObjectMeta: metav1.ObjectMeta{
59+
Name: name,
60+
Namespace: namespace,
61+
},
62+
Spec: imagev1.ImageStreamSpec{
63+
LookupPolicy: imagev1.ImageLookupPolicy{
64+
Local: true,
65+
},
66+
},
67+
Status: imagev1.ImageStreamStatus{
68+
Tags: []imagev1.NamedTagEventList{{
69+
Tag: tag,
70+
Items: []imagev1.TagEvent{{
71+
DockerImageReference: dockerImageReference,
72+
}},
73+
}},
74+
},
75+
}
76+
}
77+
78+
var newNotebook = func(annotations map[string]string, initialImage string) *nbv1.Notebook {
79+
return &nbv1.Notebook{
80+
ObjectMeta: metav1.ObjectMeta{
81+
Name: Name,
82+
Namespace: Namespace,
83+
Annotations: annotations,
84+
},
85+
Spec: nbv1.NotebookSpec{
86+
Template: nbv1.NotebookTemplateSpec{
87+
Spec: corev1.PodSpec{Containers: []corev1.Container{{
88+
Name: Name,
89+
Image: initialImage,
90+
}}},
91+
},
92+
},
93+
}
94+
}
95+
96+
// https://go.dev/wiki/TableDrivenTests
5197
testCases := []struct {
52-
name string
53-
imageStream *imagev1.ImageStream
54-
notebook *nbv1.Notebook
98+
name string
99+
100+
imageStreams []*imagev1.ImageStream
101+
notebook *nbv1.Notebook
102+
55103
// currently we expect that Notebook CR is always created,
56-
// and when unable to resolve imagestream, image: is left alone
104+
// and when unable to resolve ImageStream, image: is left alone
57105
expectedImage string
58106
// see https://www.youtube.com/watch?v=prLRI3VEVq4 for Observability Driven Development intro
59107
expectedEvents []string
60108
unexpectedEvents []string
61109
}{
62110
{
63-
name: "ImageStream with all that is needful",
64-
imageStream: &imagev1.ImageStream{
111+
name: "ImageStream with all that is needful is correctly resolved",
112+
113+
// The first test case does not use the CR creation helpers
114+
// so that everything going in is fully visible
115+
imageStreams: []*imagev1.ImageStream{{
65116
TypeMeta: metav1.TypeMeta{
66117
Kind: "ImageStream",
67118
APIVersion: "image.openshift.io/v1",
@@ -92,7 +143,7 @@ var _ = Describe("The Openshift Notebook webhook", func() {
92143
}},
93144
}},
94145
},
95-
},
146+
}},
96147
notebook: &nbv1.Notebook{
97148
ObjectMeta: metav1.ObjectMeta{
98149
Name: Name,
@@ -111,14 +162,16 @@ var _ = Describe("The Openshift Notebook webhook", func() {
111162
}}}},
112163
},
113164
},
165+
114166
expectedImage: "quay.io/modh/odh-generic-data-science-notebook@sha256:76e6af79c601a323f75a58e7005de0beac66b8cccc3d2b67efb6d11d85f0cfa1",
115167
unexpectedEvents: []string{
116168
IMAGE_STREAM_NOT_FOUND_EVENT,
117169
},
118170
},
119171
{
120172
name: "ImageStream with a tag without items (RHOAIENG-13916)",
121-
imageStream: &imagev1.ImageStream{
173+
174+
imageStreams: []*imagev1.ImageStream{{
122175
TypeMeta: metav1.TypeMeta{
123176
Kind: "ImageStream",
124177
APIVersion: "image.openshift.io/v1",
@@ -144,86 +197,96 @@ var _ = Describe("The Openshift Notebook webhook", func() {
144197
}},
145198
}},
146199
},
147-
},
148-
notebook: &nbv1.Notebook{
149-
ObjectMeta: metav1.ObjectMeta{
150-
Name: Name,
151-
Namespace: Namespace,
152-
Annotations: map[string]string{
153-
"notebooks.opendatahub.io/last-image-selection": "some-image:some-tag",
154-
},
155-
},
156-
Spec: nbv1.NotebookSpec{
157-
Template: nbv1.NotebookTemplateSpec{
158-
Spec: corev1.PodSpec{Containers: []corev1.Container{{
159-
Name: Name,
160-
Image: ":some-tag",
161-
}}}},
162-
},
163-
},
200+
}},
201+
notebook: newNotebook(map[string]string{
202+
"notebooks.opendatahub.io/last-image-selection": "some-image:some-tag",
203+
}, ":some-tag"),
204+
164205
// there is no update to the Notebook
165206
expectedImage: ":some-tag",
166207
expectedEvents: []string{
167208
IMAGE_STREAM_TAG_NOT_FOUND_EVENT,
168209
},
169210
},
211+
//
212+
// Tests for RHOAIENG-23609 project-scoped workbench imagestreams feature,
213+
// tasks RHOAIENG-23765, and RHOAIENG-25707
214+
//
170215
{
171216
name: "ImageStream in the same namespace as the Notebook",
172-
imageStream: &imagev1.ImageStream{
173-
TypeMeta: metav1.TypeMeta{
174-
Kind: "ImageStream",
175-
APIVersion: "image.openshift.io/v1",
176-
},
177-
ObjectMeta: metav1.ObjectMeta{
178-
Name: "some-image",
179-
Namespace: Namespace,
180-
},
181-
Spec: imagev1.ImageStreamSpec{
182-
LookupPolicy: imagev1.ImageLookupPolicy{
183-
Local: true,
184-
},
185-
},
186-
Status: imagev1.ImageStreamStatus{
187-
Tags: []imagev1.NamedTagEventList{{
188-
Tag: "some-tag",
189-
Items: []imagev1.TagEvent{{
190-
Created: toMetav1Time("2025-05-14T02:36:21Z"),
191-
DockerImageReference: "quay.io/modh/odh-generic-data-science-notebook@sha256:5999547f847ca841fe067ff84e2972d2cbae598066c2418e236448e115c1728e",
192-
Image: "sha256:5999547f847ca841fe067ff84e2972d2cbae598066c2418e236448e115c1728e",
193-
Generation: 2,
194-
}},
195-
Conditions: []imagev1.TagEventCondition{{
196-
Type: "ImportSuccess",
197-
Status: "False",
198-
LastTransitionTime: toMetav1Time("2025-05-14T02:36:21Z"),
199-
Reason: "NotFound",
200-
}},
201-
}},
202-
},
203-
},
204-
notebook: &nbv1.Notebook{
205-
ObjectMeta: metav1.ObjectMeta{
206-
Name: Name,
207-
Namespace: Namespace,
208-
Annotations: map[string]string{
209-
"notebooks.opendatahub.io/last-image-selection": "some-image:some-tag",
210-
"opendatahub.io/workbench-image-namespace": Namespace,
211-
},
212-
},
213-
Spec: nbv1.NotebookSpec{
214-
Template: nbv1.NotebookTemplateSpec{
215-
Spec: corev1.PodSpec{Containers: []corev1.Container{{
216-
Name: Name,
217-
Image: ":some-tag",
218-
}}}},
219-
},
217+
218+
imageStreams: []*imagev1.ImageStream{
219+
newImageStream("some-image", Namespace, "some-tag", "quay.io/modh/odh-generic-data-science-notebook@sha256:5999547f847ca841fe067ff84e2972d2cbae598066c2418e236448e115c1728e"),
220220
},
221-
// valid new image is picked from user namespace.
221+
notebook: newNotebook(map[string]string{
222+
"notebooks.opendatahub.io/last-image-selection": "some-image:some-tag",
223+
"opendatahub.io/workbench-image-namespace": Namespace,
224+
}, ":some-tag"),
225+
222226
expectedImage: "quay.io/modh/odh-generic-data-science-notebook@sha256:5999547f847ca841fe067ff84e2972d2cbae598066c2418e236448e115c1728e",
223227
unexpectedEvents: []string{
224228
IMAGE_STREAM_NOT_FOUND_EVENT,
225229
},
226230
},
231+
//
232+
// The following test cases require
233+
// multiple ImageStreams to be created.
234+
//
235+
{
236+
name: "last-image-selection is specified, workbench-image-namespace is also specified",
237+
238+
imageStreams: []*imagev1.ImageStream{
239+
newImageStream("some-image", Namespace, "some-tag", "quay.io/workbench-namespace-imagestream:latest"),
240+
newImageStream("some-image", odhNotebookControllerTestNamespace, "some-tag", "quay.io/operator-namespace-imagestream:latest"),
241+
},
242+
notebook: newNotebook(map[string]string{
243+
"notebooks.opendatahub.io/last-image-selection": "some-image:some-tag",
244+
"opendatahub.io/workbench-image-namespace": Namespace,
245+
}, ":some-tag"),
246+
247+
// image is picked from notebook namespace
248+
expectedImage: "quay.io/workbench-namespace-imagestream:latest",
249+
unexpectedEvents: []string{
250+
IMAGE_STREAM_NOT_FOUND_EVENT,
251+
},
252+
},
253+
{
254+
name: "last-image-selection is specified, workbench-image-namespace is not specified",
255+
256+
imageStreams: []*imagev1.ImageStream{
257+
newImageStream("some-image", Namespace, "some-tag", "quay.io/workbench-namespace-imagestream:latest"),
258+
newImageStream("some-image", odhNotebookControllerTestNamespace, "some-tag", "quay.io/operator-namespace-imagestream:latest"),
259+
},
260+
notebook: newNotebook(map[string]string{
261+
"notebooks.opendatahub.io/last-image-selection": "some-image:some-tag",
262+
"opendatahub.io/workbench-image-namespace": "",
263+
}, ":some-tag"),
264+
265+
// image is picked from operator namespace
266+
expectedImage: "quay.io/operator-namespace-imagestream:latest",
267+
unexpectedEvents: []string{
268+
IMAGE_STREAM_NOT_FOUND_EVENT,
269+
},
270+
},
271+
//
272+
// Something that Dashboard should never do, but we want to check it nevertheless
273+
//
274+
{
275+
name: "last-image-selection is not specified",
276+
277+
imageStreams: []*imagev1.ImageStream{
278+
newImageStream("some-image", Namespace, "some-tag", "quay.io/modh/odh-generic-data-science-notebook@sha256:5999547f847ca841fe067ff84e2972d2cbae598066c2418e236448e115c1728e"),
279+
},
280+
notebook: newNotebook(map[string]string{
281+
"opendatahub.io/workbench-image-namespace": Namespace,
282+
}, ":some-tag"),
283+
284+
// there is no update to the Notebook
285+
expectedImage: ":some-tag",
286+
unexpectedEvents: []string{
287+
IMAGE_STREAM_NOT_FOUND_EVENT,
288+
},
289+
},
227290
}
228291

229292
BeforeEach(func() {
@@ -233,10 +296,15 @@ var _ = Describe("The Openshift Notebook webhook", func() {
233296

234297
for _, testCase := range testCases {
235298
Context(fmt.Sprintf("The Notebook webhook test case: %s", testCase.name), func() {
299+
BeforeEach(func() {
300+
By("Creating the requisite ImageStream resources successfully")
301+
for _, imageStream := range testCase.imageStreams {
302+
Expect(cli.Create(ctx, imageStream, &client.CreateOptions{})).To(Succeed())
303+
}
304+
})
236305
It("Should create a Notebook resource successfully", func() {
237-
By("Creating a Notebook resource successfully")
238-
Expect(cli.Create(ctx, testCase.imageStream, &client.CreateOptions{})).To(Succeed())
239306
// if our webhook panics, then cli.Create will err
307+
By("Creating a Notebook resource successfully")
240308
Expect(cli.Create(ctx, testCase.notebook, &client.CreateOptions{})).To(Succeed())
241309

242310
By("Checking that the webhook modified the notebook CR with the expected image")
@@ -259,7 +327,9 @@ var _ = Describe("The Openshift Notebook webhook", func() {
259327
AfterEach(func() {
260328
By("Deleting the created resources")
261329
Expect(cli.Delete(ctx, testCase.notebook, &client.DeleteOptions{})).To(Succeed())
262-
Expect(cli.Delete(ctx, testCase.imageStream, &client.DeleteOptions{})).To(Succeed())
330+
for _, imageStream := range testCase.imageStreams {
331+
Expect(cli.Delete(ctx, imageStream, &client.DeleteOptions{})).To(Succeed())
332+
}
263333
})
264334
})
265335
}
@@ -268,8 +338,6 @@ var _ = Describe("The Openshift Notebook webhook", func() {
268338

269339
func toMetav1Time(timeString string) metav1.Time {
270340
parsedTime, err := time.Parse(time.RFC3339, timeString)
271-
if err != nil {
272-
return metav1.Time{}
273-
}
341+
Expect(err).ToNot(HaveOccurred())
274342
return metav1.NewTime(parsedTime)
275343
}

0 commit comments

Comments
 (0)