Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b16fa09

Browse files
authored
test: Update Feast Connection workbench tests (#5794)
test: Update Workbench Connection Tests Signed-off-by: Srihari <[email protected]>
1 parent 5b787af commit b16fa09

File tree

8 files changed

+239
-191
lines changed

8 files changed

+239
-191
lines changed

infra/feast-operator/test/e2e_rhoai/feast_wb_connection_integration_test.go

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ package e2erhoai
2222

2323
import (
2424
"fmt"
25+
"time"
2526

2627
utils "github.com/feast-dev/feast/infra/feast-operator/test/utils"
2728
. "github.com/onsi/ginkgo/v2"
@@ -43,6 +44,60 @@ var _ = Describe("Feast Workbench Integration Connection Testing", Ordered, func
4344
feastCRName = "credit-scoring"
4445
)
4546

47+
// Verify feast ConfigMap
48+
verifyFeastConfigMap := func(authEnabled bool) {
49+
feastConfigMapName := "jupyter-nb-kube-3aadmin-feast-config"
50+
configMapKey := "credit_scoring_local"
51+
By(fmt.Sprintf("Listing ConfigMaps and verifying %s exists with correct content", feastConfigMapName))
52+
53+
// Build expected content based on auth type
54+
expectedContent := []string{
55+
"project: credit_scoring_local",
56+
}
57+
if authEnabled {
58+
expectedContent = append(expectedContent, "type: kubernetes")
59+
} else {
60+
expectedContent = append(expectedContent, "type: no_auth")
61+
}
62+
63+
// First, list ConfigMaps and check if target ConfigMap exists
64+
// Retry with polling since the ConfigMap may be created asynchronously
65+
const maxRetries = 5
66+
const retryInterval = 5 * time.Second
67+
var configMapExists bool
68+
var err error
69+
70+
for i := 0; i < maxRetries; i++ {
71+
exists, listErr := utils.VerifyConfigMapExistsInList(namespace, feastConfigMapName)
72+
if listErr != nil {
73+
err = listErr
74+
if i < maxRetries-1 {
75+
fmt.Printf("Failed to list ConfigMaps, retrying in %v... (attempt %d/%d)\n", retryInterval, i+1, maxRetries)
76+
time.Sleep(retryInterval)
77+
continue
78+
}
79+
} else if exists {
80+
configMapExists = true
81+
fmt.Printf("ConfigMap %s found in ConfigMap list\n", feastConfigMapName)
82+
break
83+
}
84+
85+
if i < maxRetries-1 {
86+
fmt.Printf("ConfigMap %s not found in list yet, retrying in %v... (attempt %d/%d)\n", feastConfigMapName, retryInterval, i+1, maxRetries)
87+
time.Sleep(retryInterval)
88+
}
89+
}
90+
91+
if !configMapExists {
92+
Expect(err).ToNot(HaveOccurred(), fmt.Sprintf("Failed to find ConfigMap %s in ConfigMap list after %d attempts: %v", feastConfigMapName, maxRetries, err))
93+
}
94+
95+
// Once ConfigMap exists in list, verify content (project name and auth type)
96+
err = utils.VerifyFeastConfigMapContent(namespace, feastConfigMapName, configMapKey, expectedContent)
97+
Expect(err).ToNot(HaveOccurred(), fmt.Sprintf("Failed to verify Feast ConfigMap %s content: %v", feastConfigMapName, err))
98+
fmt.Printf("Feast ConfigMap %s verified successfully with project and auth type\n", feastConfigMapName)
99+
}
100+
46101
// Parameterized test function that handles both auth and non-auth scenarios
47102
runFeastWorkbenchIntegration := func(authEnabled bool) {
48103
// Apply permissions only if auth is enabled
@@ -51,8 +106,15 @@ var _ = Describe("Feast Workbench Integration Connection Testing", Ordered, func
51106
utils.ApplyFeastPermissions(permissionFile, "/feast-data/credit_scoring_local/feature_repo/permissions.py", namespace, feastDeploymentName)
52107
}
53108

54-
// Use the shared RunNotebookTest function for common setup and execution
55-
utils.RunNotebookTest(namespace, configMapName, notebookFile, "test/e2e_rhoai/resources/feature_repo", pvcFile, rolebindingName, notebookPVC, notebookName, testDir)
109+
// Create notebook with all setup steps
110+
// Pass feastProject parameter to set the opendatahub.io/feast-config annotation
111+
utils.CreateNotebookTest(namespace, configMapName, notebookFile, "test/e2e_rhoai/resources/feature_repo", pvcFile, rolebindingName, notebookPVC, notebookName, testDir, "credit_scoring_local")
112+
113+
// Verify Feast ConfigMap was created with correct auth type
114+
verifyFeastConfigMap(authEnabled)
115+
116+
// Monitor notebook execution
117+
utils.MonitorNotebookTest(namespace, notebookName)
56118
}
57119

58120
BeforeAll(func() {

infra/feast-operator/test/e2e_rhoai/feast_wb_milvus_test.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ var _ = Describe("Feast Jupyter Notebook Testing", Ordered, func() {
3232
namespace = "test-ns-feast-wb"
3333
configMapName = "feast-wb-cm"
3434
rolebindingName = "rb-feast-test"
35-
notebookFile = "test/e2e_rhoai/resources/feast-test.ipynb"
35+
notebookFile = "test/e2e_rhoai/resources/feast-wb-milvus-test.ipynb"
3636
pvcFile = "test/e2e_rhoai/resources/pvc.yaml"
3737
notebookPVC = "jupyterhub-nb-kube-3aadmin-pvc"
3838
testDir = "/test/e2e_rhoai"
39-
notebookName = "feast-test.ipynb"
39+
notebookName = "feast-wb-milvus-test.ipynb"
4040
feastMilvusTest = "TestFeastMilvusNotebook"
4141
)
4242

@@ -54,7 +54,12 @@ var _ = Describe("Feast Jupyter Notebook Testing", Ordered, func() {
5454

5555
Context("Feast Jupyter Notebook Test", func() {
5656
It("Should create and run a "+feastMilvusTest+" successfully", func() {
57-
utils.RunNotebookTest(namespace, configMapName, notebookFile, "test/e2e_rhoai/resources/feature_repo", pvcFile, rolebindingName, notebookPVC, notebookName, testDir)
57+
// Create notebook with all setup steps
58+
// Pass empty string for feastProject to keep annotation empty
59+
utils.CreateNotebookTest(namespace, configMapName, notebookFile, "test/e2e_rhoai/resources/feature_repo", pvcFile, rolebindingName, notebookPVC, notebookName, testDir, "")
60+
61+
// Monitor notebook execution
62+
utils.MonitorNotebookTest(namespace, notebookName)
5863
})
5964
})
6065
})

infra/feast-operator/test/e2e_rhoai/feast_wb_ray_offline_store_test.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,12 @@ var _ = Describe("Feast Jupyter Notebook Testing with Ray Offline Store", Ordere
7171

7272
Context("Feast Jupyter Notebook Test with Ray Offline store", func() {
7373
It("Should create and run a "+feastRayTest+" successfully", func() {
74-
utils.RunNotebookTest(namespace, configMapName, notebookFile, "test/e2e_rhoai/resources/feature_repo", pvcFile, rolebindingName, notebookPVC, notebookName, testDir)
74+
// Create notebook with all setup steps
75+
// Pass empty string for feastProject to keep annotation empty
76+
utils.CreateNotebookTest(namespace, configMapName, notebookFile, "test/e2e_rhoai/resources/feature_repo", pvcFile, rolebindingName, notebookPVC, notebookName, testDir, "")
77+
78+
// Monitor notebook execution
79+
utils.MonitorNotebookTest(namespace, notebookName)
7580
})
7681
})
7782
})

infra/feast-operator/test/e2e_rhoai/resources/custom-nb.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@ metadata:
1313
notebooks.opendatahub.io/last-size-selection: Small
1414
opendatahub.io/link: https://jupyter-nb-kube-3aadmin-{{.Namespace}}.{{.IngressDomain}}/notebook/{{.Namespace}}/jupyter-nb-kube-3aadmin
1515
opendatahub.io/username: {{.Username}}
16+
opendatahub.io/feast-config: {{.FeastProject}}
1617
generation: 1
1718
labels:
1819
app: jupyter-nb-kube-3aadmin
1920
opendatahub.io/dashboard: "true"
2021
opendatahub.io/odh-managed: "true"
2122
opendatahub.io/user: {{.Username}}
23+
opendatahub.io/feast-integration: 'true'
2224
name: jupyter-nb-kube-3aadmin
2325
namespace: {{.Namespace}}
2426
spec:

infra/feast-operator/test/e2e_rhoai/resources/feast-wb-connection-credit-scoring.ipynb

Lines changed: 41 additions & 180 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,45 @@
1616
"print(f\"✅ Found Expected Feast version: {actual_version} in workbench\")"
1717
]
1818
},
19+
{
20+
"cell_type": "code",
21+
"execution_count": null,
22+
"metadata": {},
23+
"outputs": [],
24+
"source": [
25+
"# --- Configuration Variables ---\n",
26+
"import os \n",
27+
"\n",
28+
"# Fetch token and server directly from oc CLI\n",
29+
"import subprocess\n",
30+
"\n",
31+
"def oc(cmd):\n",
32+
" return subprocess.check_output(cmd, shell=True).decode(\"utf-8\").strip()\n",
33+
"\n",
34+
"token = oc(\"oc whoami -t\")\n",
35+
"server = oc(\"oc whoami --show-server\")\n",
36+
"namespace = os.environ.get(\"NAMESPACE\")\n"
37+
]
38+
},
39+
{
40+
"cell_type": "code",
41+
"execution_count": null,
42+
"metadata": {},
43+
"outputs": [],
44+
"source": [
45+
"!oc login --token=$token --server=$server"
46+
]
47+
},
48+
{
49+
"cell_type": "code",
50+
"execution_count": null,
51+
"metadata": {},
52+
"outputs": [],
53+
"source": [
54+
"# Add user permission to namespace\n",
55+
"!oc adm policy add-role-to-user admin $(oc whoami) -n $namespace"
56+
]
57+
},
1958
{
2059
"cell_type": "code",
2160
"execution_count": null,
@@ -34,7 +73,7 @@
3473
").read()\n",
3574
"\n",
3675
"# Save the configmap data into an environment variable (if needed)\n",
37-
"os.environ[\"CONFIGMAP_DATA\"] = yaml_content\n"
76+
"os.environ[\"CONFIGMAP_DATA\"] = yaml_content"
3877
]
3978
},
4079
{
@@ -43,186 +82,8 @@
4382
"metadata": {},
4483
"outputs": [],
4584
"source": [
46-
"#!/usr/bin/env python3\n",
47-
"import os\n",
48-
"import json\n",
49-
"import yaml\n",
50-
"from pathlib import Path\n",
51-
"from typing import Dict, List, Any, Optional\n",
5285
"from feast import FeatureStore\n",
53-
"from feast.repo_config import RepoConfig\n",
54-
"\n",
55-
"def create_feature_store_yaml(config_content: str, config_name: str) -> str:\n",
56-
" \"\"\"\n",
57-
" Create a feature_store.yaml file from config content.\n",
58-
" \n",
59-
" Args:\n",
60-
" config_content: YAML content as string\n",
61-
" config_name: Name identifier for the config (used for filename)\n",
62-
" \n",
63-
" Returns:\n",
64-
" Path to the created YAML file\n",
65-
" \"\"\"\n",
66-
" # Parse the YAML content to validate it\n",
67-
" try:\n",
68-
" config_dict = yaml.safe_load(config_content)\n",
69-
" except yaml.YAMLError as e:\n",
70-
" raise ValueError(f\"Failed to parse YAML content for {config_name}: {e}\")\n",
71-
" \n",
72-
" # Ensure required fields are present\n",
73-
" required_fields = ['project', 'registry', 'provider']\n",
74-
" for field in required_fields:\n",
75-
" if field not in config_dict:\n",
76-
" raise ValueError(f\"Failed to create config {config_name}: missing required field '{field}'\")\n",
77-
" \n",
78-
" # Create filename\n",
79-
" filename = f\"feature_store_{config_name}.yaml\"\n",
80-
" filepath = Path(filename)\n",
81-
" \n",
82-
" # Write the YAML file\n",
83-
" with open(filepath, 'w') as f:\n",
84-
" yaml.dump(config_dict, f, default_flow_style=False, sort_keys=False)\n",
85-
" \n",
86-
" return str(filepath)\n",
87-
"\n",
88-
"\n",
89-
"def create_feature_store_object(yaml_file_path: str) -> FeatureStore:\n",
90-
" \"\"\"\n",
91-
" Create a FeatureStore object from a YAML file.\n",
92-
" \n",
93-
" Args:\n",
94-
" yaml_file_path: Path to the feature_store.yaml file\n",
95-
" \n",
96-
" Returns:\n",
97-
" FeatureStore object\n",
98-
" \"\"\"\n",
99-
" try:\n",
100-
" # Create FeatureStore from the YAML file\n",
101-
" fs = FeatureStore(fs_yaml_file=Path(yaml_file_path))\n",
102-
" return fs\n",
103-
" except Exception as e:\n",
104-
" raise RuntimeError(f\"Failed to create FeatureStore object from {yaml_file_path}: {e}\")\n",
105-
"\n",
106-
"\n",
107-
"def process_client_configs(client_configs: Dict[str, str]) -> Dict[str, Dict[str, Any]]:\n",
108-
" \"\"\"\n",
109-
" Process multiple client config YAML contents and create feature stores.\n",
110-
" \n",
111-
" Args:\n",
112-
" client_configs: Dictionary mapping config names to YAML content strings\n",
113-
" \n",
114-
" Returns:\n",
115-
" Dictionary with results for each config\n",
116-
" \"\"\"\n",
117-
" results = {}\n",
118-
" created_yamls = []\n",
119-
" feature_stores = {}\n",
120-
" \n",
121-
" print(\"Creating feature store YAMLs and objects...\")\n",
122-
" print(\"=\" * 50)\n",
123-
" \n",
124-
" for config_name, config_content in client_configs.items():\n",
125-
" try:\n",
126-
" print(f\"\\nProcessing config: {config_name}\")\n",
127-
"\n",
128-
" # Create YAML file\n",
129-
" yaml_path = create_feature_store_yaml(config_content, config_name)\n",
130-
" created_yamls.append(yaml_path)\n",
131-
" print(f\"✓ Created YAML file: {yaml_path}\")\n",
132-
" \n",
133-
" # Create FeatureStore object\n",
134-
" fs = create_feature_store_object(yaml_path)\n",
135-
" fs_var_name = f\"fs_{fs.project}\"\n",
136-
" globals()[fs_var_name] = fs\n",
137-
" feature_stores[config_name] = fs_var_name\n",
138-
" print(f\"✓ Created FeatureStore object: {fs_var_name}\")\n",
139-
"\n",
140-
" results[config_name] = {\n",
141-
" 'yaml_path': yaml_path,\n",
142-
" 'feature_store': fs_var_name,\n",
143-
" 'project_name': fs.project,\n",
144-
" 'success': True,\n",
145-
" 'error': None\n",
146-
" }\n",
147-
" \n",
148-
" except Exception as e:\n",
149-
" print(f\"✗ Failed to process config {config_name}: {e}\")\n",
150-
" results[config_name] = {\n",
151-
" 'yaml_path': None,\n",
152-
" 'feature_store': None,\n",
153-
" 'project_name': None,\n",
154-
" 'success': False,\n",
155-
" 'error': str(e)\n",
156-
" }\n",
157-
" \n",
158-
" return results\n",
159-
"\n",
160-
"\n",
161-
"def print_summary(results: Dict[str, Dict[str, Any]]) -> None:\n",
162-
" \"\"\"\n",
163-
" Print summary of all operations.\n",
164-
" \n",
165-
" Args:\n",
166-
" results: Results dictionary from process_client_configs\n",
167-
" \"\"\"\n",
168-
" print(\"\\n\" + \"=\" * 50)\n",
169-
" print(\"SUMMARY\")\n",
170-
" print(\"=\" * 50)\n",
171-
"\n",
172-
" successful_configs = [name for name, result in results.items() if result['success']]\n",
173-
" failed_configs = [name for name, result in results.items() if not result['success']]\n",
174-
" print(f\"\\n\\n✓✓Feature Store YAML files have been created in: {os.getcwd()}\")\n",
175-
" print(f\"\\n✓ Successfully processed {len(successful_configs)} config(s):\")\n",
176-
" for config_name in successful_configs:\n",
177-
" result = results[config_name]\n",
178-
" print(f\" - {config_name}: {result['yaml_path']} (Project: {result['project_name']})\")\n",
179-
"\n",
180-
" if failed_configs:\n",
181-
" print(f\"\\n✗ Failed to process {len(failed_configs)} config(s):\")\n",
182-
" for config_name in failed_configs:\n",
183-
" result = results[config_name]\n",
184-
" print(f\" - {config_name}: {result['error']}\")\n",
185-
"\n",
186-
" print(f\"\\n\\n✓✓ Feature Store Object(s) details:\")\n",
187-
" for config_name in successful_configs:\n",
188-
" result = results[config_name]\n",
189-
" print(f\"> Object Name - {result['feature_store']} ; project name - {result['project_name']} ; yaml path - {result['yaml_path']}\")\n",
190-
"\n",
191-
" print(\"\\n\")\n",
192-
" print(\"=\" * 25, \"Usage:\", \"=\" * 25)\n",
193-
" print(\"You can now use feature store object(s) to access the feature store resources and functions!\")\n",
194-
" print(\"\\n// Note: Replace object_name with the actual object name from the list above.\")\n",
195-
" print(\"object_name.list_features()\\nobject_name.get_historical_features()\")\n",
196-
" print(\"=\" * 58)\n",
197-
"\n",
198-
"\n",
199-
"def main():\n",
200-
" \"\"\"\n",
201-
" Main function to demonstrate usage with example configs.\n",
202-
" \"\"\"\n",
203-
" yaml_content = os.getenv(\"CONFIGMAP_DATA\")\n",
204-
"\n",
205-
" if not yaml_content:\n",
206-
" raise ValueError(\"CONFIGMAP_DATA environment variable is not set.\")\n",
207-
"\n",
208-
" # Use environment YAML as config\n",
209-
" client_configs = {\n",
210-
" \"feast_credit_scoring_client\": yaml_content\n",
211-
" }\n",
212-
"\n",
213-
" print(\"=\" * 50)\n",
214-
" print(\"This script will create feature store YAMLs and objects from client configs.\")\n",
215-
" print(f\"Processing {len(client_configs)} selected configurations...\")\n",
216-
" \n",
217-
" # Process the configs\n",
218-
" results = process_client_configs(client_configs)\n",
219-
" \n",
220-
" # Print summary\n",
221-
" print_summary(results)\n",
222-
"\n",
223-
"\n",
224-
"if __name__ == \"__main__\":\n",
225-
" main()"
86+
"fs_credit_scoring_local = FeatureStore(fs_yaml_file='/opt/app-root/src/feast-config/credit_scoring_local')"
22687
]
22788
},
22889
{

infra/feast-operator/test/e2e_rhoai/resources/feast-test.ipynb renamed to infra/feast-operator/test/e2e_rhoai/resources/feast-wb-milvus-test.ipynb

File renamed without changes.

0 commit comments

Comments
 (0)