Skip to content

Commit fe0f90e

Browse files
committed
test: Add Feast Milvus Jupyter Notebook Execution for downstream testing
Signed-off-by: Srihari <svenkata@redhat.com>
1 parent a83dd85 commit fe0f90e

File tree

9 files changed

+1115
-0
lines changed

9 files changed

+1115
-0
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
Copyright 2025 Feast Community.
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 e2erhoai
18+
19+
import (
20+
"fmt"
21+
"testing"
22+
23+
. "github.com/onsi/ginkgo/v2"
24+
. "github.com/onsi/gomega"
25+
)
26+
27+
// Run e2e feast Notebook tests using the Ginkgo runner.
28+
func TestRHOAIE2E(t *testing.T) {
29+
RegisterFailHandler(Fail)
30+
_, _ = fmt.Fprintf(GinkgoWriter, "Feast Jupyter Notebook Test suite\n")
31+
RunSpecs(t, "e2erhoai Feast Notebook test suite")
32+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
Copyright 2025 Feast Community.
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 e2erhoai
18+
19+
import (
20+
"fmt"
21+
"os"
22+
"os/exec"
23+
"strings"
24+
25+
utils "github.com/feast-dev/feast/infra/feast-operator/test/utils"
26+
. "github.com/onsi/ginkgo/v2"
27+
. "github.com/onsi/gomega"
28+
)
29+
30+
var _ = Describe("Feast Jupyter Notebook Testing", Ordered, func() {
31+
const (
32+
namespace = "test-ns-feast-wb"
33+
configMapName = "feast-wb-cm"
34+
rolebindingName = "rb-feast-test"
35+
notebookFile = "test/e2e_rhoai/resources/feast-test.ipynb"
36+
pvcFile = "test/e2e_rhoai/resources/pvc.yaml"
37+
notebookPVC = "jupyterhub-nb-kube-3aadmin-pvc"
38+
testDir = "/test/e2e_rhoai"
39+
notebookName = "feast-test.ipynb"
40+
feastMilvusTest = "TestFeastMilvusNotebook"
41+
)
42+
43+
BeforeAll(func() {
44+
By(fmt.Sprintf("Creating test namespace: %s", namespace))
45+
Expect(utils.CreateNamespace(namespace, testDir)).To(Succeed())
46+
fmt.Printf("Namespace %s created successfully\n", namespace)
47+
})
48+
49+
AfterAll(func() {
50+
By(fmt.Sprintf("Deleting test namespace: %s", namespace))
51+
Expect(utils.DeleteNamespace(namespace, testDir)).To(Succeed())
52+
fmt.Printf("Namespace %s deleted successfully\n", namespace)
53+
})
54+
55+
runNotebookTest := func() {
56+
env := func(key string) string {
57+
val, _ := os.LookupEnv(key)
58+
return val
59+
}
60+
61+
username := utils.GetOCUser(testDir)
62+
63+
// create config map
64+
By(fmt.Sprintf("Creating Config map: %s", configMapName))
65+
cmd := exec.Command("kubectl", "create", "configmap", configMapName, "-n", namespace, "--from-file="+notebookFile, "--from-file=test/e2e_rhoai/resources/feature_repo")
66+
output, err := utils.Run(cmd, "/test/e2e_rhoai")
67+
Expect(err).ToNot(HaveOccurred(), fmt.Sprintf(
68+
"Failed to create ConfigMap %s.\nError: %v\nOutput: %s\n",
69+
configMapName, err, output,
70+
))
71+
fmt.Printf("ConfigMap %s created successfully\n", configMapName)
72+
73+
// create pvc
74+
By("Creating Persistent volume claim: jupyterhub-nb-kube-3aadmin-pvc")
75+
cmd = exec.Command("kubectl", "apply", "-f", "test/e2e_rhoai/resources/pvc.yaml", "-n", namespace)
76+
_, err = utils.Run(cmd, "/test/e2e_rhoai")
77+
ExpectWithOffset(1, err).NotTo(HaveOccurred())
78+
fmt.Println("Persistent Volume Claim jupyterhub-nb-kube-3aadmin-pvc created successfully")
79+
80+
// create rolebinding
81+
By(fmt.Sprintf("Creating rolebinding %s for the user", rolebindingName))
82+
cmd = exec.Command("kubectl", "create", "rolebinding", rolebindingName, "-n", namespace, "--role=admin", "--user="+username)
83+
_, err = utils.Run(cmd, "/test/e2e_rhoai")
84+
ExpectWithOffset(1, err).NotTo(HaveOccurred())
85+
fmt.Printf("Created rolebinding %s successfully\n", rolebindingName)
86+
87+
// configure papermill notebook command execution
88+
command := []string{
89+
"/bin/sh",
90+
"-c",
91+
fmt.Sprintf(
92+
"pip install papermill && "+
93+
"mkdir -p /opt/app-root/src/feature_repo && "+
94+
"cp -rL /opt/app-root/notebooks/* /opt/app-root/src/feature_repo/ && "+
95+
"oc login --token=%s --server=%s --insecure-skip-tls-verify=true && "+
96+
"(papermill /opt/app-root/notebooks/%s /opt/app-root/src/output.ipynb --kernel python3 && "+
97+
"echo '✅ Notebook executed successfully' || "+
98+
"(echo '❌ Notebook execution failed' && "+
99+
"cp /opt/app-root/src/output.ipynb /opt/app-root/src/failed_output.ipynb && "+
100+
"echo '📄 Copied failed notebook to failed_output.ipynb')) && "+
101+
"jupyter nbconvert --to notebook --stdout /opt/app-root/src/output.ipynb || echo '⚠️ nbconvert failed' && "+
102+
"sleep 100; exit 0",
103+
utils.GetOCToken("test/e2e_rhoai"),
104+
utils.GetOCServer("test/e2e_rhoai"),
105+
"feast-test.ipynb",
106+
),
107+
}
108+
109+
// Defining notebook parameters
110+
nbParams := utils.NotebookTemplateParams{
111+
Namespace: namespace,
112+
IngressDomain: utils.GetIngressDomain(testDir),
113+
OpenDataHubNamespace: env("APPLICATIONS_NAMESPACE"),
114+
NotebookImage: env("NOTEBOOK_IMAGE"),
115+
NotebookConfigMapName: configMapName,
116+
NotebookPVC: notebookPVC,
117+
Username: username,
118+
OC_TOKEN: utils.GetOCToken(testDir),
119+
OC_SERVER: utils.GetOCServer(testDir),
120+
NotebookFile: notebookName,
121+
Command: "[\"" + strings.Join(command, "\",\"") + "\"]",
122+
PipIndexUrl: env("PIP_INDEX_URL"),
123+
PipTrustedHost: env("PIP_TRUSTED_HOST"),
124+
FeastVerison: env("FEAST_VERSION"),
125+
OpenAIAPIKey: env("OPENAI_API_KEY"),
126+
}
127+
128+
By("Creating Jupyter Notebook")
129+
Expect(utils.CreateNotebook(nbParams)).To(Succeed(), "Failed to create notebook")
130+
131+
By("Monitoring notebook logs")
132+
Expect(utils.MonitorNotebookPod(namespace, "jupyter-nb-", notebookName)).To(Succeed(), "Notebook execution failed")
133+
}
134+
135+
Context("Feast Jupyter Notebook Test", func() {
136+
It("Should create and run a "+feastMilvusTest+" successfully", runNotebookTest)
137+
})
138+
})
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# This template maybe used to spin up a custom notebook image
2+
# i.e.: sed s/{{.IngressDomain}}/$(oc get ingresses.config/cluster -o jsonpath={.spec.domain})/g tests/resources/custom-nb.template | oc apply -f -
3+
# resources generated:
4+
# pod/jupyter-nb-kube-3aadmin-0
5+
# service/jupyter-nb-kube-3aadmin
6+
# route.route.openshift.io/jupyter-nb-kube-3aadmin (jupyter-nb-kube-3aadmin-opendatahub.apps.tedbig412.cp.fyre.ibm.com)
7+
# service/jupyter-nb-kube-3aadmin-tls
8+
apiVersion: kubeflow.org/v1
9+
kind: Notebook
10+
metadata:
11+
annotations:
12+
notebooks.opendatahub.io/inject-oauth: "true"
13+
notebooks.opendatahub.io/last-size-selection: Small
14+
notebooks.opendatahub.io/oauth-logout-url: https://odh-dashboard-{{.OpenDataHubNamespace}}.{{.IngressDomain}}/notebookController/kube-3aadmin/home
15+
opendatahub.io/link: https://jupyter-nb-kube-3aadmin-{{.Namespace}}.{{.IngressDomain}}/notebook/{{.Namespace}}/jupyter-nb-kube-3aadmin
16+
opendatahub.io/username: {{.Username}}
17+
generation: 1
18+
labels:
19+
app: jupyter-nb-kube-3aadmin
20+
opendatahub.io/dashboard: "true"
21+
opendatahub.io/odh-managed: "true"
22+
opendatahub.io/user: {{.Username}}
23+
name: jupyter-nb-kube-3aadmin
24+
namespace: {{.Namespace}}
25+
spec:
26+
template:
27+
spec:
28+
affinity:
29+
nodeAffinity:
30+
preferredDuringSchedulingIgnoredDuringExecution:
31+
- preference:
32+
matchExpressions:
33+
- key: nvidia.com/gpu.present
34+
operator: NotIn
35+
values:
36+
- "true"
37+
weight: 1
38+
containers:
39+
- env:
40+
- name: NOTEBOOK_ARGS
41+
value: |-
42+
--ServerApp.port=8888
43+
--ServerApp.token=''
44+
--ServerApp.password=''
45+
--ServerApp.base_url=/notebook/test-feast-wb/jupyter-nb-kube-3aadmin
46+
--ServerApp.quit_button=False
47+
--ServerApp.tornado_settings={"user":"{{.Username}}","hub_host":"https://odh-dashboard-{{.OpenDataHubNamespace}}.{{.IngressDomain}}","hub_prefix":"/notebookController/{{.Username}}"}
48+
- name: JUPYTER_IMAGE
49+
value: {{.NotebookImage}}
50+
- name: JUPYTER_NOTEBOOK_PORT
51+
value: "8888"
52+
- name: PIP_INDEX_URL
53+
value: {{.PipIndexUrl}}
54+
- name: PIP_TRUSTED_HOST
55+
value: {{.PipTrustedHost}}
56+
- name: FEAST_VERSION
57+
value: {{.FeastVerison}}
58+
- name: OPENAI_API_KEY
59+
value: {{.OpenAIAPIKey}}
60+
image: {{.NotebookImage}}
61+
command: {{.Command}}
62+
imagePullPolicy: Always
63+
name: jupyter-nb-kube-3aadmin
64+
ports:
65+
- containerPort: 8888
66+
name: notebook-port
67+
protocol: TCP
68+
resources:
69+
limits:
70+
cpu: "2"
71+
memory: 3Gi
72+
requests:
73+
cpu: "1"
74+
memory: 3Gi
75+
volumeMounts:
76+
- mountPath: /opt/app-root/src
77+
name: jupyterhub-nb-kube-3aadmin-pvc
78+
- mountPath: /opt/app-root/notebooks
79+
name: {{.NotebookConfigMapName}}
80+
workingDir: /opt/app-root/src
81+
- args:
82+
- --provider=openshift
83+
- --https-address=:8443
84+
- --http-address=
85+
- --openshift-service-account=jupyter-nb-kube-3aadmin
86+
- --cookie-secret-file=/etc/oauth/config/cookie_secret
87+
- --cookie-expire=24h0m0s
88+
- --tls-cert=/etc/tls/private/tls.crt
89+
- --tls-key=/etc/tls/private/tls.key
90+
- --upstream=http://localhost:8888
91+
- --upstream-ca=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
92+
- --skip-auth-regex=^(?:/notebook/test-feast-wb/jupyter-nb-kube-3aadmin)?/api$
93+
- --email-domain=*
94+
- --skip-provider-button
95+
- --openshift-sar={"verb":"get","resource":"notebooks","resourceAPIGroup":"kubeflow.org","resourceName":"jupyter-nb-kube-3aadmin","namespace":$(NAMESPACE)}
96+
- --logout-url=https://odh-dashboard-{{.OpenDataHubNamespace}}.{{.IngressDomain}}/notebookController/kube-3aadmin/home
97+
env:
98+
- name: NAMESPACE
99+
valueFrom:
100+
fieldRef:
101+
fieldPath: metadata.namespace
102+
image: registry.redhat.io/openshift4/ose-oauth-proxy:v4.10
103+
imagePullPolicy: Always
104+
livenessProbe:
105+
failureThreshold: 3
106+
httpGet:
107+
path: /oauth/healthz
108+
port: oauth-proxy
109+
scheme: HTTPS
110+
initialDelaySeconds: 30
111+
periodSeconds: 5
112+
successThreshold: 1
113+
timeoutSeconds: 1
114+
name: oauth-proxy
115+
ports:
116+
- containerPort: 8443
117+
name: oauth-proxy
118+
protocol: TCP
119+
readinessProbe:
120+
failureThreshold: 3
121+
httpGet:
122+
path: /oauth/healthz
123+
port: oauth-proxy
124+
scheme: HTTPS
125+
initialDelaySeconds: 5
126+
periodSeconds: 5
127+
successThreshold: 1
128+
timeoutSeconds: 1
129+
resources:
130+
limits:
131+
cpu: 100m
132+
memory: 64Mi
133+
requests:
134+
cpu: 100m
135+
memory: 64Mi
136+
volumeMounts:
137+
- mountPath: /etc/oauth/config
138+
name: oauth-config
139+
- mountPath: /etc/tls/private
140+
name: tls-certificates
141+
enableServiceLinks: false
142+
serviceAccountName: jupyter-nb-kube-3aadmin
143+
volumes:
144+
- name: jupyterhub-nb-kube-3aadmin-pvc
145+
persistentVolumeClaim:
146+
claimName: {{.NotebookPVC}}
147+
- name: oauth-config
148+
secret:
149+
defaultMode: 420
150+
secretName: jupyter-nb-kube-3aadmin-oauth-config
151+
- name: tls-certificates
152+
secret:
153+
defaultMode: 420
154+
secretName: jupyter-nb-kube-3aadmin-tls
155+
- name: {{.NotebookConfigMapName}}
156+
configMap:
157+
name: {{.NotebookConfigMapName}}

0 commit comments

Comments
 (0)