Repository: kaito-project/kaito

Issue:
docs: add search to website
website needs search integration setup through algolia

https://docusaurus.io/docs/search#using-algolia-docsearch

example: https://github.com/project-copacetic/copacetic/pull/1082

Hints from issue comments:
@kaito-pr-agent: ### :bulb: Auto-generated documentation-based answer:
The documentation website for KAITO can be found at [KAITO Documentation](https://kaito-project.github.io/kaito/docs/installation).

#### Relevant Sources:

> - https://github.com/kaito-project/kaito/blob/main/README.md
@blazethunderstorm: @sozercan can I work on this issue @sozercan: @blazethunderstorm thanks for your interest! this might be good for one of the maintainers to do it since it requires credentials. 

Solution patch:
diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml
index 786b343..35081b5 100644
--- a/.github/workflows/deploy-docs.yml
+++ b/.github/workflows/deploy-docs.yml
@@ -60,6 +60,7 @@ jobs:
       env:
         ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }}
         ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }}
+        ALGOLIA_SITE_VERIFICATION: ${{ secrets.ALGOLIA_SITE_VERIFICATION }}
 
     - name: Deploy to GitHub Pages
       uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0
diff --git a/.github/workflows/ragengine-e2e-workflow.yml b/.github/workflows/ragengine-e2e-workflow.yml
index 4e965d6..0834339 100644
--- a/.github/workflows/ragengine-e2e-workflow.yml
+++ b/.github/workflows/ragengine-e2e-workflow.yml
@@ -120,6 +120,21 @@ jobs:
           AZURE_CLUSTER_NAME: ${{ env.CLUSTER_NAME }}
           RAGENGINE_SERVICE_IMG_TAG: ${{ env.VERSION }}
 
+      - name: Create node pool for RAG workloads
+        uses: azure/CLI@v2.1.0
+        with:
+          inlineScript: |
+            az aks nodepool add \
+              --resource-group "${{ env.CLUSTER_NAME }}" \
+              --cluster-name "${{ env.CLUSTER_NAME }}" \
+              --name ragpool \
+              -s Standard_D8_v3 \
+              -c 1 \
+              --labels apps=phi-3
+        env:
+          AZURE_RESOURCE_GROUP: ${{ env.CLUSTER_NAME }}
+          AZURE_CLUSTER_NAME: ${{ env.CLUSTER_NAME }}
+
       - name: Run e2e test
         run: |
           AI_MODELS_REGISTRY=$E2E_ACR_AMRT_USERNAME.azurecr.io \
diff --git a/api/v1alpha1/ragengine_types.go b/api/v1alpha1/ragengine_types.go
index f60b982..827ebc5 100644
--- a/api/v1alpha1/ragengine_types.go
+++ b/api/v1alpha1/ragengine_types.go
@@ -99,10 +99,11 @@ type RAGEngineStatus struct {
 // RAGEngine is the Schema for the ragengine API
 // +kubebuilder:object:root=true
 // +kubebuilder:subresource:status
-// +kubebuilder:resource:path=ragengines,scope=Namespaced,categories=ragengine
+// +kubebuilder:resource:path=ragengines,scope=Namespaced,categories=ragengine,shortName=rag
 // +kubebuilder:storageversion
 // +kubebuilder:printcolumn:name="Instance",type="string",JSONPath=".spec.compute.instanceType",description=""
 // +kubebuilder:printcolumn:name="ResourceReady",type="string",JSONPath=".status.conditions[?(@.type==\"ResourceReady\")].status",description=""
+// +kubebuilder:printcolumn:name="ServiceReady",type="string",JSONPath=".status.conditions[?(@.type==\"ServiceReady\")].status",description=""
 // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description=""
 type RAGEngine struct {
 	metav1.TypeMeta   `json:",inline"`
diff --git a/charts/kaito/ragengine/crds/kaito.sh_ragengines.yaml b/charts/kaito/ragengine/crds/kaito.sh_ragengines.yaml
index ff7e1cc..72c6a4d 100644
--- a/charts/kaito/ragengine/crds/kaito.sh_ragengines.yaml
+++ b/charts/kaito/ragengine/crds/kaito.sh_ragengines.yaml
@@ -13,6 +13,8 @@ spec:
     kind: RAGEngine
     listKind: RAGEngineList
     plural: ragengines
+    shortNames:
+    - rag
     singular: ragengine
   scope: Namespaced
   versions:
@@ -23,6 +25,9 @@ spec:
     - jsonPath: .status.conditions[?(@.type=="ResourceReady")].status
       name: ResourceReady
       type: string
+    - jsonPath: .status.conditions[?(@.type=="ServiceReady")].status
+      name: ServiceReady
+      type: string
     - jsonPath: .metadata.creationTimestamp
       name: Age
       type: date
diff --git a/config/crd/bases/kaito.sh_ragengines.yaml b/config/crd/bases/kaito.sh_ragengines.yaml
index ff7e1cc..72c6a4d 100644
--- a/config/crd/bases/kaito.sh_ragengines.yaml
+++ b/config/crd/bases/kaito.sh_ragengines.yaml
@@ -13,6 +13,8 @@ spec:
     kind: RAGEngine
     listKind: RAGEngineList
     plural: ragengines
+    shortNames:
+    - rag
     singular: ragengine
   scope: Namespaced
   versions:
@@ -23,6 +25,9 @@ spec:
     - jsonPath: .status.conditions[?(@.type=="ResourceReady")].status
       name: ResourceReady
       type: string
+    - jsonPath: .status.conditions[?(@.type=="ServiceReady")].status
+      name: ServiceReady
+      type: string
     - jsonPath: .metadata.creationTimestamp
       name: Age
       type: date
diff --git a/pkg/ragengine/controllers/preset-rag.go b/pkg/ragengine/controllers/preset-rag.go
index ed1cf0c..608fe21 100644
--- a/pkg/ragengine/controllers/preset-rag.go
+++ b/pkg/ragengine/controllers/preset-rag.go
@@ -25,6 +25,7 @@ import (
 
 	"github.com/kaito-project/kaito/api/v1alpha1"
 	"github.com/kaito-project/kaito/pkg/ragengine/manifests"
+	"github.com/kaito-project/kaito/pkg/sku"
 	"github.com/kaito-project/kaito/pkg/utils"
 	"github.com/kaito-project/kaito/pkg/utils/consts"
 	"github.com/kaito-project/kaito/pkg/utils/resources"
@@ -116,15 +117,20 @@ func CreatePresetRAG(ctx context.Context, ragEngineObj *v1alpha1.RAGEngine, revi
 
 	if ragEngineObj.Spec.Embedding.Local != nil {
 		var skuNumGPUs int
-		gpuConfig, err := utils.GetGPUConfigBySKU(ragEngineObj.Spec.Compute.InstanceType)
-		if err != nil {
-			gpuConfig, err = utils.TryGetGPUConfigFromNode(ctx, kubeClient, ragEngineObj.Status.WorkerNodes)
-			if err != nil {
-				skuNumGPUs = 1
-			}
+		var gpuConfig *sku.GPUConfig = nil
+		var err error
+		if len(ragEngineObj.Spec.Compute.PreferredNodes) == 0 {
+			gpuConfig, _ = utils.GetGPUConfigBySKU(ragEngineObj.Spec.Compute.InstanceType)
 		}
 		if gpuConfig != nil {
 			skuNumGPUs = gpuConfig.GPUCount
+		} else {
+			gpuConfig, err = utils.TryGetGPUConfigFromNode(ctx, kubeClient, ragEngineObj.Status.WorkerNodes)
+			if err != nil {
+				skuNumGPUs = 0
+			} else if gpuConfig != nil {
+				skuNumGPUs = gpuConfig.GPUCount
+			}
 		}
 
 		resourceReq = corev1.ResourceRequirements{
diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js
index 6ec011a..32fa8ec 100644
--- a/website/docusaurus.config.js
+++ b/website/docusaurus.config.js
@@ -12,6 +12,18 @@ const config = {
     tagline: 'Kubernetes AI Toolchain Operator',
     favicon: 'img/favicon.ico',
 
+    headTags: [
+        {
+            tagName: "meta",
+            attributes: {
+                // Allow Algolia crawler to index the site
+                // See https://www.algolia.com/doc/tools/crawler/getting-started/create-crawler/#verify-your-domain.
+                name: "algolia-site-verification",
+                content: process.env.ALGOLIA_SITE_VERIFICATION || "DUMMY_SITE_VERIFICATION",
+            }
+        },
+    ],
+
     // Set the production url of your site here
     url: 'https://kaito-project.github.io',
     // Set the /<baseUrl>/ pathname under which your site is served



Test patch:
diff --git a/pkg/ragengine/controllers/preset-rag_test.go b/pkg/ragengine/controllers/preset-rag_test.go
index ca6acec..d24453b 100644
--- a/pkg/ragengine/controllers/preset-rag_test.go
+++ b/pkg/ragengine/controllers/preset-rag_test.go
@@ -20,8 +20,11 @@ import (
 
 	"github.com/stretchr/testify/mock"
 	appsv1 "k8s.io/api/apps/v1"
+	corev1 "k8s.io/api/core/v1"
 
+	"github.com/kaito-project/kaito/api/v1alpha1"
 	"github.com/kaito-project/kaito/pkg/utils/consts"
+	"github.com/kaito-project/kaito/pkg/utils/resources"
 	"github.com/kaito-project/kaito/pkg/utils/test"
 )
 
@@ -93,3 +96,67 @@ func TestCreatePresetRAG(t *testing.T) {
 		})
 	}
 }
+
+func TestGPUConfigLogic(t *testing.T) {
+	test.RegisterTestModel()
+
+	testcases := map[string]struct {
+		nodeCount        int
+		callMocks        func(c *test.MockClient)
+		ragEngine        *v1alpha1.RAGEngine
+		expectedGPUReq   int64
+		expectedLimitReq int64
+	}{
+		"test-rag-model": {
+			callMocks: func(c *test.MockClient) {
+				c.On("Create", mock.IsType(context.TODO()), mock.IsType(&appsv1.Deployment{}), mock.Anything).Return(nil)
+			},
+			ragEngine:        test.MockRAGEngineWithPreset,
+			expectedGPUReq:   int64(2),
+			expectedLimitReq: int64(2),
+		},
+		"test-rag-preferred-nodes": {
+			callMocks: func(c *test.MockClient) {
+				c.On("Create", mock.IsType(context.TODO()), mock.IsType(&appsv1.Deployment{}), mock.Anything).Return(nil)
+			},
+			ragEngine:        test.MockRAGEngineWithPresetPreferredCPUNodes,
+			expectedGPUReq:   int64(0),
+			expectedLimitReq: int64(0),
+		},
+	}
+
+	for k, tc := range testcases {
+		t.Run(k, func(t *testing.T) {
+			t.Setenv("CLOUD_PROVIDER", consts.AzureCloudName)
+
+			mockClient := test.NewClient()
+			tc.callMocks(mockClient)
+
+			ragEngineObj := tc.ragEngine
+			createdObject, _ := CreatePresetRAG(context.TODO(), ragEngineObj, "1", mockClient)
+
+			resourceReq := createdObject.(*appsv1.Deployment).Spec.Template.Spec.Containers[0].Resources
+
+			gpuRequests, exists := resourceReq.Requests[corev1.ResourceName(resources.CapacityNvidiaGPU)]
+			if !exists {
+				t.Errorf("GPU requests not found in resource requirements")
+				return
+			}
+
+			gpuLimits, exists := resourceReq.Limits[corev1.ResourceName(resources.CapacityNvidiaGPU)]
+			if !exists {
+				t.Errorf("GPU limits not found in resource requirements")
+				return
+			}
+
+			gpuRequestCount := gpuRequests.Value()
+			gpuLimitCount := gpuLimits.Value()
+			if gpuRequestCount != tc.expectedGPUReq {
+				t.Errorf("%s: GPU request count is not expected, got %d, expected %d", k, gpuRequestCount, tc.expectedGPUReq)
+			}
+			if gpuLimitCount != tc.expectedLimitReq {
+				t.Errorf("%s: GPU limit count is not expected, got %d, expected %d", k, gpuLimitCount, tc.expectedLimitReq)
+			}
+		})
+	}
+}
diff --git a/pkg/utils/test/testUtils.go b/pkg/utils/test/testUtils.go
index a2deaf7..d036df6 100644
--- a/pkg/utils/test/testUtils.go
+++ b/pkg/utils/test/testUtils.go
@@ -331,6 +331,33 @@ var (
 			},
 		},
 	}
+	MockRAGEngineWithPresetPreferredCPUNodes = &v1alpha1.RAGEngine{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "testRAGEngine",
+			Namespace: "kaito",
+			UID:       "test-uid",
+		},
+		Spec: &v1alpha1.RAGEngineSpec{
+			Compute: &v1alpha1.ResourceSpec{
+				Count:          &gpuNodeCount,
+				PreferredNodes: []string{"aks-ragpool-00000000-vmss000000"},
+				InstanceType:   "Standard_NC24ads_A100_v4",
+				LabelSelector: &metav1.LabelSelector{
+					MatchLabels: map[string]string{
+						"ragengine.kaito.io/name": "testRAGEngine",
+					},
+				},
+			},
+			Embedding: &v1alpha1.EmbeddingSpec{
+				Local: &v1alpha1.LocalEmbeddingSpec{
+					ModelID: "BAAI/bge-small-en-v1.5",
+				},
+			},
+			InferenceService: &v1alpha1.InferenceServiceSpec{
+				URL: "http://localhost:5000/chat",
+			},
+		},
+	}
 	MockRAGEngineWithRevision1 = &v1alpha1.RAGEngine{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:        "testRAGEngine",
diff --git a/test/rage2e/rag_test.go b/test/rage2e/rag_test.go
index d1aae84..d85dc7b 100644
--- a/test/rage2e/rag_test.go
+++ b/test/rage2e/rag_test.go
@@ -102,7 +102,7 @@ var _ = Describe("RAGEngine", func() {
 		validateInferenceandRAGResource(ragengineObj.ObjectMeta, int32(numOfReplica), false)
 		validateRAGEngineCondition(ragengineObj, string(kaitov1alpha1.RAGEngineConditionTypeSucceeded), "ragengine to be ready")
 
-		indexDoc, err := createAndValidateIndexPod(ragengineObj)
+		indexDoc, err := createAndValidateIndexPod(ragengineObj, "index-pod")
 		Expect(err).NotTo(HaveOccurred(), "Failed to create and validate IndexPod")
 		Expect(indexDoc).NotTo(BeNil(), "Index document should not be nil")
 		Expect(indexDoc["doc_id"]).NotTo(BeNil(), "Index document ID should not be nil")
@@ -110,24 +110,24 @@ var _ = Describe("RAGEngine", func() {
 		docID := indexDoc["doc_id"].(string)
 
 		searchQuerySuccess := "\\n\\nKaito is an operator that is designed to automate the AI/ML model inference or tuning workload in a Kubernetes cluster."
-		err = createAndValidateQueryPod(ragengineObj, searchQuerySuccess, true)
+		err = createAndValidateQueryPod(ragengineObj, searchQuerySuccess, true, "query-pod")
 		Expect(err).NotTo(HaveOccurred(), "Failed to create and validate QueryPod")
 
 		persistLogSuccess := "Successfully persisted index kaito"
-		err = createAndValidatePersistPod(ragengineObj, persistLogSuccess)
+		err = createAndValidatePersistPod(ragengineObj, persistLogSuccess, "persist-pod")
 		Expect(err).NotTo(HaveOccurred(), "Failed to create and validate PersistPod")
 
 		loadLogSuccess := "Successfully loaded index kaito"
-		err = createAndValidateLoadPod(ragengineObj, loadLogSuccess)
+		err = createAndValidateLoadPod(ragengineObj, loadLogSuccess, "load-pod")
 		Expect(err).NotTo(HaveOccurred(), "Failed to create and validate LoadPod")
 
-		err = createAndValidateUpdateDocumentPod(ragengineObj, docID)
+		err = createAndValidateUpdateDocumentPod(ragengineObj, docID, "update-document-pod")
 		Expect(err).NotTo(HaveOccurred(), "Failed to create and validate UpdateDocumentPod")
 
-		err = createAndValidateDeleteDocumentPod(ragengineObj, docID)
+		err = createAndValidateDeleteDocumentPod(ragengineObj, docID, "delete-document-pod")
 		Expect(err).NotTo(HaveOccurred(), "Failed to create and validate DeleteDocumentPod")
 
-		err = createAndValidateDeleteIndexPod(ragengineObj)
+		err = createAndValidateDeleteIndexPod(ragengineObj, "delete-index-pod")
 		Expect(err).NotTo(HaveOccurred(), "Failed to create and validate DeleteIndexPod")
 	})
 
@@ -165,7 +165,7 @@ var _ = Describe("RAGEngine", func() {
 		validateInferenceandRAGResource(ragengineObj.ObjectMeta, int32(numOfReplica), false)
 		validateRAGEngineCondition(ragengineObj, string(kaitov1alpha1.RAGEngineConditionTypeSucceeded), "ragengine to be ready")
 
-		indexDoc, err := createAndValidateIndexPod(ragengineObj)
+		indexDoc, err := createAndValidateIndexPod(ragengineObj, "index-pod")
 		Expect(err).NotTo(HaveOccurred(), "Failed to create and validate IndexPod")
 		Expect(indexDoc).NotTo(BeNil(), "Index document should not be nil")
 		Expect(indexDoc["doc_id"]).NotTo(BeNil(), "Index document ID should not be nil")
@@ -173,24 +173,91 @@ var _ = Describe("RAGEngine", func() {
 		docID := indexDoc["doc_id"].(string)
 
 		searchQuerySuccess := "\\nKaito is an operator that automates the AI/ML model inference or tuning workload in a Kubernetes cluster.\\n\\n\\n"
-		err = createAndValidateQueryPod(ragengineObj, searchQuerySuccess, false)
+		err = createAndValidateQueryPod(ragengineObj, searchQuerySuccess, false, "query-pod")
 		Expect(err).NotTo(HaveOccurred(), "Failed to create and validate QueryPod")
 
 		persistLogSuccess := "Successfully persisted index kaito"
-		err = createAndValidatePersistPod(ragengineObj, persistLogSuccess)
+		err = createAndValidatePersistPod(ragengineObj, persistLogSuccess, "persist-pod")
 		Expect(err).NotTo(HaveOccurred(), "Failed to create and validate PersistPod")
 
 		loadLogSuccess := "Successfully loaded index kaito"
-		err = createAndValidateLoadPod(ragengineObj, loadLogSuccess)
+		err = createAndValidateLoadPod(ragengineObj, loadLogSuccess, "load-pod")
 		Expect(err).NotTo(HaveOccurred(), "Failed to create and validate LoadPod")
 
-		err = createAndValidateUpdateDocumentPod(ragengineObj, docID)
+		err = createAndValidateUpdateDocumentPod(ragengineObj, docID, "update-document-pod")
 		Expect(err).NotTo(HaveOccurred(), "Failed to create and validate UpdateDocumentPod")
 
-		err = createAndValidateDeleteDocumentPod(ragengineObj, docID)
+		err = createAndValidateDeleteDocumentPod(ragengineObj, docID, "delete-document-pod")
 		Expect(err).NotTo(HaveOccurred(), "Failed to create and validate DeleteDocumentPod")
 
-		err = createAndValidateDeleteIndexPod(ragengineObj)
+		err = createAndValidateDeleteIndexPod(ragengineObj, "delete-index-pod")
+		Expect(err).NotTo(HaveOccurred(), "Failed to create and validate DeleteIndexPod")
+	})
+
+	It("should create RAG with preferred nodes and kaito VLLM workspace successfully", utils.GinkgoLabelFastCheck, func() {
+		numOfReplica := 1
+		workspaceObj := createPhi3WorkspaceWithPresetPublicModeAndVLLM(numOfReplica)
+
+		time.Sleep(30 * time.Second)
+
+		validateWorkspaceResourceStatus(workspaceObj)
+
+		validateAssociatedService(workspaceObj.ObjectMeta)
+
+		validateInferenceandRAGResource(workspaceObj.ObjectMeta, int32(numOfReplica), false)
+
+		validateWorkspaceReadiness(workspaceObj)
+
+		serviceName := workspaceObj.Name
+		serviceNamespace := workspaceObj.Namespace
+		service := &v1.Service{}
+
+		_ = utils.TestingCluster.KubeClient.Get(ctx, client.ObjectKey{
+			Namespace: serviceNamespace,
+			Name:      serviceName,
+		}, service)
+
+		clusterIP := service.Spec.ClusterIP
+
+		preferredNode, err := getRagPoolNode()
+		Expect(err).NotTo(HaveOccurred(), "Failed to get ragpool node")
+
+		// Get the preferred node from ragpool
+		ragengineObj := createLocalPreferredNodesRAGEngine(clusterIP, preferredNode)
+
+		defer cleanupResources(workspaceObj, ragengineObj)
+
+		validateRAGEngineCondition(ragengineObj, string(kaitov1alpha1.ConditionTypeResourceStatus), "ragengineObj resource status to be ready")
+		validateAssociatedService(ragengineObj.ObjectMeta)
+		validateInferenceandRAGResource(ragengineObj.ObjectMeta, int32(numOfReplica), false)
+		validateRAGEngineCondition(ragengineObj, string(kaitov1alpha1.RAGEngineConditionTypeSucceeded), "ragengine to be ready")
+
+		indexDoc, err := createAndValidateIndexPod(ragengineObj, "index-pod")
+		Expect(err).NotTo(HaveOccurred(), "Failed to create and validate IndexPod")
+		Expect(indexDoc).NotTo(BeNil(), "Index document should not be nil")
+		Expect(indexDoc["doc_id"]).NotTo(BeNil(), "Index document ID should not be nil")
+		Expect(indexDoc["text"]).NotTo(BeNil(), "Index document text should not be nil")
+		docID := indexDoc["doc_id"].(string)
+
+		searchQuerySuccess := "\\nKaito is an operator that automates the AI/ML model inference or tuning workload in a Kubernetes cluster.\\n\\n\\n"
+		err = createAndValidateQueryPod(ragengineObj, searchQuerySuccess, false, "query-pod")
+		Expect(err).NotTo(HaveOccurred(), "Failed to create and validate QueryPod")
+
+		persistLogSuccess := "Successfully persisted index kaito"
+		err = createAndValidatePersistPod(ragengineObj, persistLogSuccess, "persist-pod")
+		Expect(err).NotTo(HaveOccurred(), "Failed to create and validate PersistPod")
+
+		loadLogSuccess := "Successfully loaded index kaito"
+		err = createAndValidateLoadPod(ragengineObj, loadLogSuccess, "load-pod")
+		Expect(err).NotTo(HaveOccurred(), "Failed to create and validate LoadPod")
+
+		err = createAndValidateUpdateDocumentPod(ragengineObj, docID, "update-document-pod")
+		Expect(err).NotTo(HaveOccurred(), "Failed to create and validate UpdateDocumentPod")
+
+		err = createAndValidateDeleteDocumentPod(ragengineObj, docID, "delete-document-pod")
+		Expect(err).NotTo(HaveOccurred(), "Failed to create and validate DeleteDocumentPod")
+
+		err = createAndValidateDeleteIndexPod(ragengineObj, "delete-index-pod")
 		Expect(err).NotTo(HaveOccurred(), "Failed to create and validate DeleteIndexPod")
 	})
 
@@ -257,6 +324,27 @@ func GenerateLocalEmbeddingRAGEngineManifest(name, namespace, instanceType, embe
 	}
 }
 
+func GenerateLocalEmbeddingRAGEngineManifestWithPreferredNodes(name, namespace, preferredNodes, embeddingModelID string, labelSelector *metav1.LabelSelector, inferenceSpec *kaitov1alpha1.InferenceServiceSpec) *kaitov1alpha1.RAGEngine {
+	return &kaitov1alpha1.RAGEngine{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      name,
+			Namespace: namespace,
+		},
+		Spec: &kaitov1alpha1.RAGEngineSpec{
+			Compute: &kaitov1alpha1.ResourceSpec{
+				PreferredNodes: []string{preferredNodes},
+				LabelSelector:  labelSelector,
+			},
+			Embedding: &kaitov1alpha1.EmbeddingSpec{
+				Local: &kaitov1alpha1.LocalEmbeddingSpec{
+					ModelID: embeddingModelID,
+				},
+			},
+			InferenceService: inferenceSpec,
+		},
+	}
+}
+
 // validateWorkspaceReadiness validates workspace readiness
 func validateWorkspaceReadiness(workspaceObj *kaitov1beta1.Workspace) {
 	By("Checking the workspace status is ready", func() {
@@ -298,6 +386,25 @@ func createLocalEmbeddingKaitoVLLMRAGEngine(baseURL string) *kaitov1alpha1.RAGEn
 	return ragEngineObj
 }
 
+func createLocalPreferredNodesRAGEngine(baseURL, preferredNode string) *kaitov1alpha1.RAGEngine {
+	ragEngineObj := &kaitov1alpha1.RAGEngine{}
+	serviceURL := fmt.Sprintf("http://%s/v1/completions", baseURL)
+	By("Creating RAG with localembedding and kaito vllm inference", func() {
+		uniqueID := fmt.Sprint("rag-", rand.Intn(1000))
+		ragEngineObj = GenerateLocalEmbeddingRAGEngineManifestWithPreferredNodes(uniqueID, namespaceName, preferredNode, "BAAI/bge-small-en-v1.5",
+			&metav1.LabelSelector{
+				MatchLabels: map[string]string{"apps": "phi-3"},
+			},
+			&kaitov1alpha1.InferenceServiceSpec{
+				URL: serviceURL,
+			},
+		)
+
+		createAndValidateRAGEngine(ragEngineObj)
+	})
+	return ragEngineObj
+}
+
 func createLocalEmbeddingHFURLRAGEngine() *kaitov1alpha1.RAGEngine {
 	ragEngineObj := &kaitov1alpha1.RAGEngine{}
 	hfURL := "https://api-inference.huggingface.co/models/HuggingFaceH4/zephyr-7b-beta/v1/completions"
@@ -516,9 +623,8 @@ func deleteWorkspace(workspaceObj *kaitov1beta1.Workspace) error {
 	return nil
 }
 
-func createAndValidateIndexPod(ragengineObj *kaitov1alpha1.RAGEngine) (map[string]any, error) {
+func createAndValidateIndexPod(ragengineObj *kaitov1alpha1.RAGEngine, podName string) (map[string]any, error) {
 	indexResp := []map[string]any{}
-	podName := "index-pod"
 	By("Creating index pod", func() {
 		curlCommand := `curl -X POST ` + ragengineObj.Name + `:80/index \
 -H "Content-Type: application/json" \
@@ -582,8 +688,7 @@ func createAndValidateIndexPod(ragengineObj *kaitov1alpha1.RAGEngine) (map[strin
 	return indexResp[0], nil
 }
 
-func createAndValidateUpdateDocumentPod(ragengineObj *kaitov1alpha1.RAGEngine, docID string) error {
-	podName := "update-document-pod"
+func createAndValidateUpdateDocumentPod(ragengineObj *kaitov1alpha1.RAGEngine, docID string, podName string) error {
 	By("Creating update document pod", func() {
 		curlCommand := `curl -X POST ` + ragengineObj.Name + `:80/indexes/kaito/documents \
 -H "Content-Type: application/json" \
@@ -626,8 +731,7 @@ func createAndValidateUpdateDocumentPod(ragengineObj *kaitov1alpha1.RAGEngine, d
 	return nil
 }
 
-func createAndValidateDeleteDocumentPod(ragengineObj *kaitov1alpha1.RAGEngine, docID string) error {
-	podName := "delete-document-pod"
+func createAndValidateDeleteDocumentPod(ragengineObj *kaitov1alpha1.RAGEngine, docID string, podName string) error {
 	By("Creating delete document pod", func() {
 		curlCommand := `curl -X POST ` + ragengineObj.Name + `:80/indexes/kaito/documents/delete \
 -H "Content-Type: application/json" \
@@ -662,8 +766,7 @@ func createAndValidateDeleteDocumentPod(ragengineObj *kaitov1alpha1.RAGEngine, d
 	return nil
 }
 
-func createAndValidateDeleteIndexPod(ragengineObj *kaitov1alpha1.RAGEngine) error {
-	podName := "delete-index-pod"
+func createAndValidateDeleteIndexPod(ragengineObj *kaitov1alpha1.RAGEngine, podName string) error {
 	By("Creating delete index pod", func() {
 		curlCommand := `curl -X DELETE ` + ragengineObj.Name + `:80/indexes/kaito \
 -H "Content-Type: application/json"`
@@ -697,8 +800,7 @@ func createAndValidateDeleteIndexPod(ragengineObj *kaitov1alpha1.RAGEngine) erro
 	return nil
 }
 
-func createAndValidateQueryPod(ragengineObj *kaitov1alpha1.RAGEngine, expectedSearchQueries string, remote bool) error {
-	podName := "query-pod"
+func createAndValidateQueryPod(ragengineObj *kaitov1alpha1.RAGEngine, expectedSearchQueries string, remote bool, podName string) error {
 	By("Creating query pod", func() {
 		var curlCommand string
 		// Note: Request without model specified should still succeed with vLLM. As model name is dynamically fetched.
@@ -766,8 +868,7 @@ func createAndValidateQueryPod(ragengineObj *kaitov1alpha1.RAGEngine, expectedSe
 	return nil
 }
 
-func createAndValidatePersistPod(ragengineObj *kaitov1alpha1.RAGEngine, expectedPersistResult string) error {
-	podName := "persist-pod"
+func createAndValidatePersistPod(ragengineObj *kaitov1alpha1.RAGEngine, expectedPersistResult string, podName string) error {
 	By("Creating Persist pod", func() {
 		curlCommand := `curl -X POST ` + ragengineObj.Name + `:80/persist/kaito`
 		pod := GenerateCURLPodManifest(podName, curlCommand, ragengineObj.Namespace)
@@ -810,8 +911,7 @@ func createAndValidatePersistPod(ragengineObj *kaitov1alpha1.RAGEngine, expected
 	return nil
 }
 
-func createAndValidateLoadPod(ragengineObj *kaitov1alpha1.RAGEngine, expectedLoadResult string) error {
-	podName := "load-pod"
+func createAndValidateLoadPod(ragengineObj *kaitov1alpha1.RAGEngine, expectedLoadResult string, podName string) error {
 	By("Creating Load Pod", func() {
 		curlCommand := `curl -X POST ` + ragengineObj.Name + `:80/load/kaito?overwrite=True`
 		pod := GenerateCURLPodManifest(podName, curlCommand, ragengineObj.Namespace)
@@ -899,3 +999,19 @@ func createAndValidateSecret() {
 		})
 	})
 }
+
+func getRagPoolNode() (string, error) {
+	nodeList := &v1.NodeList{}
+	err := utils.TestingCluster.KubeClient.List(ctx, nodeList)
+	if err != nil {
+		return "", fmt.Errorf("failed to list nodes: %v", err)
+	}
+
+	for _, node := range nodeList.Items {
+		if strings.Contains(node.Name, "ragpool") {
+			return node.Name, nil
+		}
+	}
+
+	return "", fmt.Errorf("no node containing 'ragpool' found")
+}