-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat: Added support for setting env vars in feast services in feast controller #4739
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,7 @@ package controller | |
| import ( | ||
| "context" | ||
| "encoding/base64" | ||
| "reflect" | ||
|
|
||
| . "github.com/onsi/ginkgo/v2" | ||
| . "github.com/onsi/gomega" | ||
|
|
@@ -382,7 +383,9 @@ var _ = Describe("FeatureStore Controller", func() { | |
| Context("When reconciling a resource with all services enabled", func() { | ||
| const resourceName = "services" | ||
| image := "test:latest" | ||
| var pullPolicy corev1.PullPolicy = corev1.PullAlways | ||
| var pullPolicy = corev1.PullAlways | ||
| var testEnvVarName = "testEnvVarName" | ||
| var testEnvVarValue = "testEnvVarValue" | ||
|
|
||
| ctx := context.Background() | ||
|
|
||
|
|
@@ -396,29 +399,8 @@ var _ = Describe("FeatureStore Controller", func() { | |
| By("creating the custom resource for the Kind FeatureStore") | ||
| err := k8sClient.Get(ctx, typeNamespacedName, featurestore) | ||
| if err != nil && errors.IsNotFound(err) { | ||
| resource := &feastdevv1alpha1.FeatureStore{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: resourceName, | ||
| Namespace: "default", | ||
| }, | ||
| Spec: feastdevv1alpha1.FeatureStoreSpec{ | ||
| FeastProject: feastProject, | ||
| Services: &feastdevv1alpha1.FeatureStoreServices{ | ||
| OfflineStore: &feastdevv1alpha1.OfflineStore{}, | ||
| OnlineStore: &feastdevv1alpha1.OnlineStore{ | ||
| ServiceConfigs: feastdevv1alpha1.ServiceConfigs{ | ||
| DefaultConfigs: feastdevv1alpha1.DefaultConfigs{ | ||
| Image: &image, | ||
| }, | ||
| OptionalConfigs: feastdevv1alpha1.OptionalConfigs{ | ||
| ImagePullPolicy: &pullPolicy, | ||
| Resources: &corev1.ResourceRequirements{}, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| } | ||
| resource := createFeatureStoreResource(resourceName, image, pullPolicy, &[]corev1.EnvVar{{Name: testEnvVarName, Value: testEnvVarValue}, | ||
| {Name: "fieldRefName", ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.namespace"}}}}) | ||
| Expect(k8sClient.Create(ctx, resource)).To(Succeed()) | ||
| } | ||
| }) | ||
|
|
@@ -463,6 +445,7 @@ var _ = Describe("FeatureStore Controller", func() { | |
| Expect(resource.Status.Applied.Services.OfflineStore.Resources).To(BeNil()) | ||
| Expect(resource.Status.Applied.Services.OfflineStore.Image).To(Equal(&services.DefaultImage)) | ||
| Expect(resource.Status.Applied.Services.OnlineStore).NotTo(BeNil()) | ||
| Expect(resource.Status.Applied.Services.OnlineStore.Env).To(Equal(&[]corev1.EnvVar{{Name: testEnvVarName, Value: testEnvVarValue}, {Name: "fieldRefName", ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.namespace"}}}})) | ||
| Expect(resource.Status.Applied.Services.OnlineStore.ImagePullPolicy).To(Equal(&pullPolicy)) | ||
| Expect(resource.Status.Applied.Services.OnlineStore.Resources).NotTo(BeNil()) | ||
| Expect(resource.Status.Applied.Services.OnlineStore.Image).To(Equal(&image)) | ||
|
|
@@ -658,7 +641,7 @@ var _ = Describe("FeatureStore Controller", func() { | |
| deploy) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
| Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(1)) | ||
| Expect(deploy.Spec.Template.Spec.Containers[0].Env).To(HaveLen(1)) | ||
| Expect(deploy.Spec.Template.Spec.Containers[0].Env).To(HaveLen(3)) | ||
| Expect(deploy.Spec.Template.Spec.Containers[0].ImagePullPolicy).To(Equal(corev1.PullAlways)) | ||
| env = getFeatureStoreYamlEnvVar(deploy.Spec.Template.Spec.Containers[0].Env) | ||
| Expect(env).NotTo(BeNil()) | ||
|
|
@@ -689,6 +672,7 @@ var _ = Describe("FeatureStore Controller", func() { | |
| Registry: regRemote, | ||
| } | ||
| Expect(repoConfigOnline).To(Equal(onlineConfig)) | ||
| Expect(deploy.Spec.Template.Spec.Containers[0].Env).To(HaveLen(3)) | ||
|
|
||
| // check client config | ||
| cm := &corev1.ConfigMap{} | ||
|
|
@@ -751,6 +735,89 @@ var _ = Describe("FeatureStore Controller", func() { | |
| Expect(repoConfig).To(Equal(testConfig)) | ||
| }) | ||
|
|
||
| It("should properly set container env variables", func() { | ||
| By("Reconciling the created resource") | ||
| controllerReconciler := &FeatureStoreReconciler{ | ||
| Client: k8sClient, | ||
| Scheme: k8sClient.Scheme(), | ||
| } | ||
|
|
||
| _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ | ||
| NamespacedName: typeNamespacedName, | ||
| }) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
|
|
||
| resource := &feastdevv1alpha1.FeatureStore{} | ||
| err = k8sClient.Get(ctx, typeNamespacedName, resource) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
|
|
||
| req, err := labels.NewRequirement(services.NameLabelKey, selection.Equals, []string{resource.Name}) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
| labelSelector := labels.NewSelector().Add(*req) | ||
| listOpts := &client.ListOptions{Namespace: resource.Namespace, LabelSelector: labelSelector} | ||
| deployList := appsv1.DeploymentList{} | ||
| err = k8sClient.List(ctx, &deployList, listOpts) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
| Expect(deployList.Items).To(HaveLen(3)) | ||
|
|
||
| svcList := corev1.ServiceList{} | ||
| err = k8sClient.List(ctx, &svcList, listOpts) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
| Expect(svcList.Items).To(HaveLen(3)) | ||
|
|
||
| cmList := corev1.ConfigMapList{} | ||
| err = k8sClient.List(ctx, &cmList, listOpts) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
| Expect(cmList.Items).To(HaveLen(1)) | ||
|
|
||
| feast := services.FeastServices{ | ||
| Client: controllerReconciler.Client, | ||
| Context: ctx, | ||
| Scheme: controllerReconciler.Scheme, | ||
| FeatureStore: resource, | ||
| } | ||
|
|
||
| fsYamlStr := "" | ||
| fsYamlStr, err = feast.GetServiceFeatureStoreYamlBase64(services.OnlineFeastType) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
|
|
||
| // check online config | ||
| deploy := &appsv1.Deployment{} | ||
| err = k8sClient.Get(ctx, types.NamespacedName{ | ||
| Name: feast.GetFeastServiceName(services.OnlineFeastType), | ||
| Namespace: resource.Namespace, | ||
| }, | ||
| deploy) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
| Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(1)) | ||
| Expect(deploy.Spec.Template.Spec.Containers[0].Env).To(HaveLen(3)) | ||
| Expect(areEnvVarArraysEqual(deploy.Spec.Template.Spec.Containers[0].Env, []corev1.EnvVar{{Name: testEnvVarName, Value: testEnvVarValue}, {Name: services.FeatureStoreYamlEnvVar, Value: fsYamlStr}, {Name: "fieldRefName", ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.namespace"}}}})).To(BeTrue()) | ||
| Expect(deploy.Spec.Template.Spec.Containers[0].ImagePullPolicy).To(Equal(corev1.PullAlways)) | ||
|
|
||
| // change feast project and reconcile | ||
| resourceNew := resource.DeepCopy() | ||
| resourceNew.Spec.Services.OnlineStore.Env = &[]corev1.EnvVar{{Name: testEnvVarName, Value: testEnvVarValue + "1"}, {Name: services.FeatureStoreYamlEnvVar, Value: fsYamlStr}, {Name: "fieldRefName", ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}}}} | ||
| err = k8sClient.Update(ctx, resourceNew) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
| _, err = controllerReconciler.Reconcile(ctx, reconcile.Request{ | ||
| NamespacedName: typeNamespacedName, | ||
| }) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
|
|
||
| err = k8sClient.Get(ctx, typeNamespacedName, resource) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
| Expect(areEnvVarArraysEqual(*resource.Status.Applied.Services.OnlineStore.Env, []corev1.EnvVar{{Name: testEnvVarName, Value: testEnvVarValue + "1"}, {Name: services.FeatureStoreYamlEnvVar, Value: fsYamlStr}, {Name: "fieldRefName", ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}}}})).To(BeTrue()) | ||
| err = k8sClient.Get(ctx, types.NamespacedName{ | ||
| Name: feast.GetFeastServiceName(services.OnlineFeastType), | ||
| Namespace: resource.Namespace, | ||
| }, | ||
| deploy) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
|
|
||
| Expect(deploy.Spec.Template.Spec.Containers[0].Env).To(HaveLen(3)) | ||
| Expect(areEnvVarArraysEqual(deploy.Spec.Template.Spec.Containers[0].Env, []corev1.EnvVar{{Name: testEnvVarName, Value: testEnvVarValue + "1"}, {Name: services.FeatureStoreYamlEnvVar, Value: fsYamlStr}, {Name: "fieldRefName", ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.name"}}}})).To(BeTrue()) | ||
| }) | ||
|
|
||
| It("Should delete k8s objects owned by the FeatureStore CR", func() { | ||
| By("changing which feast services are configured in the CR") | ||
| controllerReconciler := &FeatureStoreReconciler{ | ||
|
|
@@ -1122,6 +1189,33 @@ var _ = Describe("FeatureStore Controller", func() { | |
| }) | ||
| }) | ||
|
|
||
| func createFeatureStoreResource(resourceName string, image string, pullPolicy corev1.PullPolicy, envVars *[]corev1.EnvVar) *feastdevv1alpha1.FeatureStore { | ||
| return &feastdevv1alpha1.FeatureStore{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: resourceName, | ||
| Namespace: "default", | ||
| }, | ||
| Spec: feastdevv1alpha1.FeatureStoreSpec{ | ||
| FeastProject: feastProject, | ||
| Services: &feastdevv1alpha1.FeatureStoreServices{ | ||
| OfflineStore: &feastdevv1alpha1.OfflineStore{}, | ||
| OnlineStore: &feastdevv1alpha1.OnlineStore{ | ||
| ServiceConfigs: feastdevv1alpha1.ServiceConfigs{ | ||
| DefaultConfigs: feastdevv1alpha1.DefaultConfigs{ | ||
| Image: &image, | ||
| }, | ||
| OptionalConfigs: feastdevv1alpha1.OptionalConfigs{ | ||
| Env: envVars, | ||
| ImagePullPolicy: &pullPolicy, | ||
| Resources: &corev1.ResourceRequirements{}, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| func getFeatureStoreYamlEnvVar(envs []corev1.EnvVar) *corev1.EnvVar { | ||
| for _, e := range envs { | ||
| if e.Name == services.FeatureStoreYamlEnvVar { | ||
|
|
@@ -1130,3 +1224,25 @@ func getFeatureStoreYamlEnvVar(envs []corev1.EnvVar) *corev1.EnvVar { | |
| } | ||
| return nil | ||
| } | ||
|
|
||
| func areEnvVarArraysEqual(arr1 []corev1.EnvVar, arr2 []corev1.EnvVar) bool { | ||
| if len(arr1) != len(arr2) { | ||
| return false | ||
| } | ||
|
|
||
| // Create a map to count occurrences of EnvVars in the first array. | ||
| envMap := make(map[string]corev1.EnvVar) | ||
|
|
||
| for _, env := range arr1 { | ||
| envMap[env.Name] = env | ||
| } | ||
|
|
||
| // Check the second array against the map. | ||
| for _, env := range arr2 { | ||
| if _, exists := envMap[env.Name]; !exists || !reflect.DeepEqual(envMap[env.Name], env) { | ||
| return false | ||
| } | ||
| } | ||
|
|
||
| return true | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i wonder if
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It doesn't take into account 2 arrays are the same if they have the same elements but different order:
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the main problem i see with the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now its done updated the function to
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we add an env variable test for the following type of envvar?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we also support
envFromoptions to read fromSecretsorConfigMap?@tchughesiv @tmihalac
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dmartinol that's what this added test will confirm support for...
EnvVarSourcecontainsfieldRef, as well asconfigMapKeyRef&secretKeyRefUh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is supported, there is just no specific test for that.
I don't see a lot of added value in adding those tests since the only thing we are doing is overriding the original EnvVar object from the container with the ones from the spec if they have the same name
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tchughesiv
I've added a test for
fieldRef, do you think we should add forconfigMapKeyRef&secretKeyRefas well?see my previous comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tmihalac imo, we don't need a test for the others