Skip to content

Commit 3754788

Browse files
committed
test: Add Feast Registry Server Rest Test
Signed-off-by: Srihari <svenkata@redhat.com>
1 parent 8f6555f commit 3754788

File tree

10 files changed

+1280
-0
lines changed

10 files changed

+1280
-0
lines changed
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# .github/workflows/registry-rest-api-tests.yml
2+
name: pr-rest-API-tests
3+
4+
on:
5+
push:
6+
branches:
7+
- main
8+
pull_request:
9+
types:
10+
- opened
11+
- synchronize
12+
- labeled
13+
14+
jobs:
15+
registry-rest-api-tests:
16+
timeout-minutes: 30
17+
if:
18+
((github.event.action == 'labeled' && (github.event.label.name == 'approved' || github.event.label.name == 'lgtm' || github.event.label.name == 'ok-to-test')) ||
19+
(github.event.action != 'labeled' && (contains(github.event.pull_request.labels.*.name, 'ok-to-test') || contains(github.event.pull_request.labels.*.name, 'approved') || contains(github.event.pull_request.labels.*.name, 'lgtm')))) &&
20+
github.repository == 'feast-dev/feast'
21+
runs-on: ubuntu-latest
22+
23+
services:
24+
kind:
25+
# Specify the Kubernetes version
26+
image: kindest/node:v1.30.6
27+
28+
env:
29+
KIND_CLUSTER: "registry-rest-api-cluster"
30+
31+
steps:
32+
- name: Checkout code
33+
uses: actions/checkout@v4
34+
35+
- name: Free Disk Space (Ubuntu)
36+
uses: jlumbroso/free-disk-space@v1.3.1
37+
with:
38+
android: true
39+
dotnet: true
40+
haskell: true
41+
large-packages: false
42+
docker-images: false
43+
swap-storage: false
44+
tool-cache: false
45+
46+
- name: Set up Go
47+
uses: actions/setup-go@v5
48+
with:
49+
go-version: 1.22.9
50+
51+
- name: Create KIND cluster
52+
run: |
53+
cat <<EOF | kind create cluster --name $KIND_CLUSTER --wait 10m --config=-
54+
kind: Cluster
55+
apiVersion: kind.x-k8s.io/v1alpha4
56+
nodes:
57+
- role: control-plane
58+
extraMounts:
59+
- hostPath: /mnt/kind
60+
containerPath: /var/lib/containerd
61+
extraPortMappings:
62+
- containerPort: 80
63+
hostPort: 80
64+
protocol: TCP
65+
- containerPort: 443
66+
hostPort: 443
67+
protocol: TCP
68+
kubeadmConfigPatches:
69+
- |
70+
kind: InitConfiguration
71+
nodeRegistration:
72+
kubeletExtraArgs:
73+
node-labels: "ingress-ready=true"
74+
EOF
75+
76+
- name: Set up kubernetes context
77+
run: |
78+
kubectl config use-context kind-$KIND_CLUSTER
79+
echo "kind context is switched to cluster kind-$KIND_CLUSTER"
80+
81+
82+
- name: Set up Ingress controller
83+
run: |
84+
echo "Installing ingress-nginx for KIND..."
85+
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.9.5/deploy/static/provider/kind/deploy.yaml
86+
87+
echo "⏳ Waiting for ingress controller to become ready..."
88+
kubectl wait --namespace ingress-nginx \
89+
--for=condition=Ready pod \
90+
--selector=app.kubernetes.io/component=controller \
91+
--timeout=180s
92+
93+
- name: Add ingress DNS to /etc/hosts
94+
run: |
95+
echo "127.0.0.1 feast.kind.test" | sudo tee -a /etc/hosts
96+
echo "Added 'feast.kind.test' to /etc/hosts"
97+
98+
- name: Build and Deploy Feast Operator images
99+
run: |
100+
# Create namespace
101+
kubectl create ns feast-operator-system || true
102+
103+
# navigate to feast operator path
104+
cd infra/feast-operator/
105+
106+
# Build Feast Operator Docker image
107+
make docker-build IMG=localhost/feast-operator:v0.0.1
108+
109+
# Load Operator image into KIND
110+
kind load docker-image localhost/feast-operator:v0.0.1 --name $KIND_CLUSTER
111+
112+
# Build Feast dev image
113+
make feast-ci-dev-docker-img
114+
115+
# Tag Feast image for KIND compatibility
116+
docker tag feastdev/feature-server:dev localhost/feastdev/feature-server:dev
117+
118+
# Load Feast image into KIND
119+
kind load docker-image localhost/feastdev/feature-server:dev --name $KIND_CLUSTER
120+
121+
# Install CRDs
122+
make install
123+
124+
# Deploy operator to the KIND cluster
125+
make deploy IMG=localhost/feast-operator:v0.0.1 FS_IMG=localhost/feastdev/feature-server:dev
126+
127+
# Wait for controller manager to be ready
128+
kubectl wait deployment feast-operator-controller-manager -n feast-operator-system --for=condition=Available=True --timeout=180s
129+
130+
- name: Setup Python
131+
uses: actions/setup-python@v5
132+
id: setup-python
133+
with:
134+
python-version: 3.11
135+
architecture: x64
136+
137+
- name: Install the latest version of uv
138+
uses: astral-sh/setup-uv@v5
139+
with:
140+
enable-cache: true
141+
142+
- name: Install dependencies
143+
run: make install-python-dependencies-ci
144+
145+
- name: Setup and Run Registry Rest API tests
146+
run: |
147+
echo "Running Registry REST API tests..."
148+
cd sdk/python/tests/registry_rest_api_tests/
149+
pytest test_feast_registry.py -s
150+
151+
- name: Clean up docker images
152+
if: always()
153+
run: |
154+
docker images --format '{{.Repository}}:{{.Tag}}' | grep 'feast' | xargs -r docker rmi -f
155+
docker system prune -a -f
156+
157+
- name: Debug KIND Cluster when there is a failure
158+
if: failure()
159+
run: |
160+
kubectl get pods --all-namespaces
161+
kubectl describe nodes
162+
163+
- name: Clean up
164+
if: always()
165+
run: |
166+
# Delete the KIND cluster after tests
167+
kind delete cluster --name kind-$KIND_CLUSTER
168+
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import os
2+
3+
import pytest
4+
import requests
5+
from kubernetes import client, config
6+
from support import (
7+
applyFeastProject,
8+
create_feast_project,
9+
create_namespace,
10+
create_route,
11+
delete_namespace,
12+
deploy_and_validate_pod,
13+
run_kubectl_apply_with_sed,
14+
run_kubectl_command,
15+
validate_feature_store_cr_status,
16+
)
17+
18+
19+
class FeastRestClient:
20+
def __init__(self, base_url):
21+
self.base_url = base_url.rstrip("/")
22+
self.api_prefix = "/api/v1"
23+
24+
def _build_url(self, endpoint):
25+
if not endpoint.startswith("/"):
26+
endpoint = "/" + endpoint
27+
return f"{self.base_url}{self.api_prefix}{endpoint}"
28+
29+
def get(self, endpoint, params=None):
30+
params = params or {}
31+
params.setdefault("allow_cache", "false")
32+
url = self._build_url(endpoint)
33+
return requests.get(url, params=params, verify=False)
34+
35+
36+
@pytest.fixture(scope="session")
37+
def feast_rest_client():
38+
# Load kubeconfig and initialize Kubernetes client
39+
config.load_kube_config()
40+
api_instance = client.CoreV1Api()
41+
42+
# Constants and environment values
43+
namespace = "test-ns-feast-rest"
44+
credit_scoring = "credit-scoring"
45+
driver_ranking = "driver-ranking"
46+
service_name = "feast-test-s3-registry-rest"
47+
run_on_openshift = os.getenv("RUN_ON_OPENSHIFT_CI", "false").lower() == "true"
48+
49+
# Create test namespace
50+
create_namespace(api_instance, namespace)
51+
52+
try:
53+
if not run_on_openshift:
54+
# Deploy dependencies
55+
deploy_and_validate_pod(namespace, "resource/redis.yaml", "app=redis")
56+
deploy_and_validate_pod(namespace, "resource/postgres.yaml", "app=postgres")
57+
58+
# Create and validate FeatureStore CRs
59+
create_feast_project(
60+
"resource/feast_config_credit_scoring.yaml", namespace, credit_scoring
61+
)
62+
validate_feature_store_cr_status(namespace, credit_scoring)
63+
64+
create_feast_project(
65+
"resource/feast_config_driver_ranking.yaml", namespace, driver_ranking
66+
)
67+
validate_feature_store_cr_status(namespace, driver_ranking)
68+
69+
# Deploy ingress and get route URL
70+
run_kubectl_command(
71+
["apply", "-f", "resource/feast-registry-nginx.yaml", "-n", namespace]
72+
)
73+
ingress_host = run_kubectl_command(
74+
[
75+
"get",
76+
"ingress",
77+
"feast-registry-ingress",
78+
"-n",
79+
namespace,
80+
"-o",
81+
"jsonpath={.spec.rules[0].host}",
82+
]
83+
)
84+
route_url = f"http://{ingress_host}"
85+
86+
# Apply feast projects
87+
output_cs = applyFeastProject(namespace, credit_scoring)
88+
print(f"Output of feast apply for {credit_scoring}:\n{output_cs}")
89+
90+
output_dr = applyFeastProject(namespace, driver_ranking)
91+
print(f"Output of feast apply for {driver_ranking}:\n{output_dr}")
92+
93+
else:
94+
# OpenShift cluster setup using S3-based registry
95+
aws_access_key = os.getenv("AWS_ACCESS_KEY")
96+
aws_secret_key = os.getenv("AWS_SECRET_KEY")
97+
aws_bucket = os.getenv("AWS_BUCKET_NAME")
98+
registry_path = os.getenv("AWS_REGISTRY_FILE_PATH")
99+
100+
run_kubectl_apply_with_sed(
101+
aws_access_key,
102+
aws_secret_key,
103+
aws_bucket,
104+
registry_path,
105+
"resource/feast_config_rhoai.yaml",
106+
namespace,
107+
)
108+
validate_feature_store_cr_status(namespace, "test-s3")
109+
route_url = create_route(namespace, credit_scoring, service_name)
110+
if not route_url:
111+
raise RuntimeError("Route URL could not be fetched.")
112+
113+
print(f"\n Connected to Feast REST at: {route_url}")
114+
yield FeastRestClient(route_url)
115+
116+
finally:
117+
print(f"\n Deleting namespace: {namespace}")
118+
delete_namespace(api_instance, namespace)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
apiVersion: networking.k8s.io/v1
2+
kind: Ingress
3+
metadata:
4+
name: feast-registry-ingress
5+
namespace: test-ns-feast-rest
6+
annotations:
7+
nginx.ingress.kubernetes.io/ssl-redirect: "false"
8+
spec:
9+
ingressClassName: nginx
10+
rules:
11+
- host: feast.kind.test
12+
http:
13+
paths:
14+
- path: /
15+
pathType: Prefix
16+
backend:
17+
service:
18+
name: feast-credit-scoring-registry-rest
19+
port:
20+
number: 80
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
apiVersion: v1
2+
kind: Secret
3+
metadata:
4+
name: feast-data-stores
5+
namespace: test-ns-feast-rest
6+
stringData:
7+
redis: |
8+
connection_string: redis.test-ns-feast-rest.svc.cluster.local:6379
9+
sql: |
10+
path: postgresql+psycopg://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres.test-ns-feast-rest.svc.cluster.local:5432/${POSTGRES_DB}
11+
cache_ttl_seconds: 60
12+
sqlalchemy_config_kwargs:
13+
echo: false
14+
pool_pre_ping: true
15+
---
16+
apiVersion: feast.dev/v1alpha1
17+
kind: FeatureStore
18+
metadata:
19+
name: credit-scoring
20+
namespace: test-ns-feast-rest
21+
spec:
22+
feastProject: credit_scoring_local
23+
feastProjectDir:
24+
git:
25+
url: https://github.com/feast-dev/feast-credit-score-local-tutorial
26+
ref: 598a270
27+
services:
28+
offlineStore:
29+
persistence:
30+
file:
31+
type: duckdb
32+
onlineStore:
33+
persistence:
34+
store:
35+
type: redis
36+
secretRef:
37+
name: feast-data-stores
38+
server:
39+
envFrom:
40+
- secretRef:
41+
name: postgres-secret
42+
env:
43+
- name: MPLCONFIGDIR
44+
value: /tmp
45+
resources:
46+
requests:
47+
cpu: 150m
48+
memory: 128Mi
49+
registry:
50+
local:
51+
persistence:
52+
store:
53+
type: sql
54+
secretRef:
55+
name: feast-data-stores
56+
server:
57+
envFrom:
58+
- secretRef:
59+
name: postgres-secret
60+
grpc: true
61+
restAPI: true

0 commit comments

Comments
 (0)