Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package controller
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"reflect"
"strings"
Expand Down Expand Up @@ -1233,6 +1234,127 @@ var _ = Describe("FeatureStore Controller", func() {
Expect(cond.Message).To(Equal("Error: Remote feast registry of referenced FeatureStore '" + referencedRegistry.Name + "' is not ready"))
})

It("should allow cross-project registry references with different feastProject names", func() {
By("Reconciling the primary local registry FeatureStore")
controllerReconciler := &FeatureStoreReconciler{
Client: k8sClient,
Scheme: k8sClient.Scheme(),
}
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())

primaryStore := &feastdevv1.FeatureStore{}
err = k8sClient.Get(ctx, typeNamespacedName, primaryStore)
Expect(err).NotTo(HaveOccurred())
Expect(primaryStore.Status.Applied.FeastProject).To(Equal(feastProject))

By("Creating a second FeatureStore with a DIFFERENT feastProject name referencing the first")
crossProjectName := "cross-project-ref"
crossProjectFeastName := "different_project"
crossProjectResource := &feastdevv1.FeatureStore{
ObjectMeta: metav1.ObjectMeta{
Name: crossProjectName,
Namespace: primaryStore.Namespace,
},
Spec: feastdevv1.FeatureStoreSpec{
FeastProject: crossProjectFeastName,
Services: &feastdevv1.FeatureStoreServices{
OnlineStore: &feastdevv1.OnlineStore{
Server: &feastdevv1.ServerConfigs{},
},
Registry: &feastdevv1.Registry{
Remote: &feastdevv1.RemoteRegistryConfig{
FeastRef: &feastdevv1.FeatureStoreRef{
Name: primaryStore.Name,
},
},
},
},
},
}
crossProjectResource.SetGroupVersionKind(feastdevv1.GroupVersion.WithKind("FeatureStore"))
crossProjectNsName := client.ObjectKeyFromObject(crossProjectResource)
err = k8sClient.Create(ctx, crossProjectResource)
Expect(err).NotTo(HaveOccurred())

By("Reconciling the cross-project FeatureStore — should succeed without error")
_, err = controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: crossProjectNsName,
})
Expect(err).NotTo(HaveOccurred())

err = k8sClient.Get(ctx, crossProjectNsName, crossProjectResource)
Expect(err).NotTo(HaveOccurred())

By("Verifying the cross-project FeatureStore is ready and uses its own project name")
Expect(crossProjectResource.Status.Applied.FeastProject).To(Equal(crossProjectFeastName))
Expect(crossProjectResource.Status.ServiceHostnames.Registry).To(Equal(primaryStore.Status.ServiceHostnames.Registry))
Expect(apimeta.IsStatusConditionTrue(crossProjectResource.Status.Conditions, feastdevv1.OnlineStoreReadyType)).To(BeTrue())

By("Verifying the cross-project client ConfigMap uses the correct project name and shared registry")
crossFeast := services.FeastServices{
Handler: handler.FeastHandler{
Client: controllerReconciler.Client,
Context: ctx,
Scheme: controllerReconciler.Scheme,
FeatureStore: crossProjectResource,
},
}
crossCm := &corev1.ConfigMap{}
err = k8sClient.Get(ctx, types.NamespacedName{
Name: crossFeast.GetFeastServiceName(services.ClientFeastType),
Namespace: crossProjectResource.Namespace,
}, crossCm)
Expect(err).NotTo(HaveOccurred())
crossRepoConfig := &services.RepoConfig{}
err = yaml.Unmarshal([]byte(crossCm.Data[services.FeatureStoreYamlCmKey]), crossRepoConfig)
Expect(err).NotTo(HaveOccurred())
Expect(crossRepoConfig.Project).To(Equal(crossProjectFeastName))
Expect(crossRepoConfig.Registry.Path).To(ContainSubstring(primaryStore.Name))

By("Verifying the primary store client ConfigMap still uses its own project name")
primaryFeast := services.FeastServices{
Handler: handler.FeastHandler{
Client: controllerReconciler.Client,
Context: ctx,
Scheme: controllerReconciler.Scheme,
FeatureStore: primaryStore,
},
}
primaryCm := &corev1.ConfigMap{}
err = k8sClient.Get(ctx, types.NamespacedName{
Name: primaryFeast.GetFeastServiceName(services.ClientFeastType),
Namespace: primaryStore.Namespace,
}, primaryCm)
Expect(err).NotTo(HaveOccurred())
primaryRepoConfig := &services.RepoConfig{}
err = yaml.Unmarshal([]byte(primaryCm.Data[services.FeatureStoreYamlCmKey]), primaryRepoConfig)
Expect(err).NotTo(HaveOccurred())
Expect(primaryRepoConfig.Project).To(Equal(feastProject))

By("Verifying both stores share the same registry path")
Expect(crossRepoConfig.Registry.Path).To(Equal(primaryRepoConfig.Registry.Path))

By("Verifying the namespace registry ConfigMap lists both client configs")
registryCm := &corev1.ConfigMap{}
err = k8sClient.Get(ctx, types.NamespacedName{
Name: services.NamespaceRegistryConfigMapName,
Namespace: services.DefaultKubernetesNamespace,
}, registryCm)
Expect(err).NotTo(HaveOccurred())
var registryData services.NamespaceRegistryData
err = json.Unmarshal([]byte(registryCm.Data[services.NamespaceRegistryDataKey]), &registryData)
Expect(err).NotTo(HaveOccurred())
ns := primaryStore.Namespace
Expect(registryData.Namespaces[ns]).To(ContainElement(primaryFeast.GetFeastServiceName(services.ClientFeastType)))
Expect(registryData.Namespaces[ns]).To(ContainElement(crossFeast.GetFeastServiceName(services.ClientFeastType)))

By("Cleaning up the cross-project FeatureStore")
Expect(k8sClient.Delete(ctx, crossProjectResource)).To(Succeed())
})

It("should correctly set container command args for grpc/rest modes", func() {
controllerReconciler := &FeatureStoreReconciler{
Client: k8sClient,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,16 @@ var _ = Describe("FeatureStore Controller - Feast service TLS", func() {
Expect(deploy.Spec.Replicas).To(Equal(int32Ptr(1)))
Expect(controllerutil.HasControllerReference(deploy)).To(BeTrue())
Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(4))

// verify init containers have TLS volume mounts after reconciliation
Expect(deploy.Spec.Template.Spec.InitContainers).NotTo(BeEmpty())
for _, initContainer := range deploy.Spec.Template.Spec.InitContainers {
Expect(initContainer.VolumeMounts).To(ContainElement(SatisfyAll(
HaveField("MountPath", services.GetTlsPath(services.RegistryFeastType)),
HaveField("ReadOnly", true),
)), "init container %s should have registry TLS volume mount", initContainer.Name)
}

svc := &corev1.Service{}
err = k8sClient.Get(ctx, types.NamespacedName{
Name: feast.GetFeastServiceName(services.RegistryFeastType),
Expand Down Expand Up @@ -401,6 +411,14 @@ var _ = Describe("FeatureStore Controller - Feast service TLS", func() {
Expect(err).NotTo(HaveOccurred())
Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(2))

// verify init containers have remote registry TLS volume mounts
for _, initContainer := range deploy.Spec.Template.Spec.InitContainers {
Expect(initContainer.VolumeMounts).To(ContainElement(SatisfyAll(
HaveField("MountPath", services.GetTlsPath(services.RegistryFeastType)),
HaveField("ReadOnly", true),
)), "init container %s should have remote registry TLS volume mount", initContainer.Name)
}

// check offline config
offlineContainer = services.GetOfflineContainer(*deploy)
env = getFeatureStoreYamlEnvVar(offlineContainer.Env)
Expand Down Expand Up @@ -530,6 +548,12 @@ var _ = Describe("Test mountCustomCABundle functionality", func() {
HaveField("MountPath", tlsPathCustomCABundle),
)))
}
for _, initContainer := range deploy.Spec.Template.Spec.InitContainers {
Expect(initContainer.VolumeMounts).To(ContainElement(SatisfyAll(
HaveField("Name", configMapName),
HaveField("MountPath", tlsPathCustomCABundle),
)), "init container %s should have CA bundle volume mount", initContainer.Name)
}
})

It("should not mount CA bundle volume or container mounts when ConfigMap is absent", func() {
Expand Down Expand Up @@ -570,5 +594,10 @@ var _ = Describe("Test mountCustomCABundle functionality", func() {
for _, container := range deploy.Spec.Template.Spec.Containers {
Expect(container.VolumeMounts).NotTo(ContainElement(HaveField("Name", configMapName)))
}
for _, initContainer := range deploy.Spec.Template.Spec.InitContainers {
Expect(initContainer.VolumeMounts).NotTo(ContainElement(
HaveField("Name", configMapName),
), "init container %s should not have CA bundle mount when ConfigMap is absent", initContainer.Name)
}
})
})
15 changes: 5 additions & 10 deletions infra/feast-operator/internal/controller/services/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -1154,9 +1154,6 @@ func (feast *FeastServices) getRemoteRegistryFeastHandler() (*FeastServices, err
}
return nil, err
}
if feast.Handler.FeatureStore.Status.Applied.FeastProject != remoteFeastObj.Status.Applied.FeastProject {
return nil, errors.New("FeatureStore '" + remoteFeastObj.Name + "' is using a different feast project than '" + feast.Handler.FeatureStore.Status.Applied.FeastProject + "'. Project names must match.")
}
return &FeastServices{
Handler: handler.FeastHandler{
Client: feast.Handler.Client,
Expand Down Expand Up @@ -1314,13 +1311,11 @@ func (feast *FeastServices) mountPvcConfig(podSpec *corev1.PodSpec, pvcConfig *f
},
},
})
if feastType == OfflineFeastType {
for i := range podSpec.InitContainers {
podSpec.InitContainers[i].VolumeMounts = append(podSpec.InitContainers[i].VolumeMounts, corev1.VolumeMount{
Name: volName,
MountPath: pvcConfig.MountPath,
})
}
for i := range podSpec.InitContainers {
podSpec.InitContainers[i].VolumeMounts = append(podSpec.InitContainers[i].VolumeMounts, corev1.VolumeMount{
Name: volName,
MountPath: pvcConfig.MountPath,
})
}
for i := range podSpec.Containers {
podSpec.Containers[i].VolumeMounts = append(podSpec.Containers[i].VolumeMounts, corev1.VolumeMount{
Expand Down
44 changes: 28 additions & 16 deletions infra/feast-operator/internal/controller/services/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,12 +224,16 @@ func (feast *FeastServices) mountTlsConfig(feastType FeastServiceType, podSpec *
},
},
})
tlsMount := corev1.VolumeMount{
Name: volName,
MountPath: GetTlsPath(feastType),
ReadOnly: true,
}
if i, container := getContainerByType(feastType, *podSpec); container != nil {
podSpec.Containers[i].VolumeMounts = append(podSpec.Containers[i].VolumeMounts, corev1.VolumeMount{
Name: volName,
MountPath: GetTlsPath(feastType),
ReadOnly: true,
})
podSpec.Containers[i].VolumeMounts = append(podSpec.Containers[i].VolumeMounts, tlsMount)
}
for i := range podSpec.InitContainers {
podSpec.InitContainers[i].VolumeMounts = append(podSpec.InitContainers[i].VolumeMounts, tlsMount)
}
}
}
Expand All @@ -245,12 +249,16 @@ func mountTlsRemoteRegistryConfig(podSpec *corev1.PodSpec, tls *feastdevv1.TlsRe
},
},
})
tlsMount := corev1.VolumeMount{
Name: volName,
MountPath: GetTlsPath(RegistryFeastType),
ReadOnly: true,
}
for i := range podSpec.Containers {
podSpec.Containers[i].VolumeMounts = append(podSpec.Containers[i].VolumeMounts, corev1.VolumeMount{
Name: volName,
MountPath: GetTlsPath(RegistryFeastType),
ReadOnly: true,
})
podSpec.Containers[i].VolumeMounts = append(podSpec.Containers[i].VolumeMounts, tlsMount)
}
for i := range podSpec.InitContainers {
podSpec.InitContainers[i].VolumeMounts = append(podSpec.InitContainers[i].VolumeMounts, tlsMount)
}
}
}
Expand All @@ -267,13 +275,17 @@ func (feast *FeastServices) mountCustomCABundle(podSpec *corev1.PodSpec) {
},
})

caMount := corev1.VolumeMount{
Name: customCaBundle.VolumeName,
MountPath: tlsPathCustomCABundle,
ReadOnly: true,
SubPath: "ca-bundle.crt",
}
for i := range podSpec.Containers {
podSpec.Containers[i].VolumeMounts = append(podSpec.Containers[i].VolumeMounts, corev1.VolumeMount{
Name: customCaBundle.VolumeName,
MountPath: tlsPathCustomCABundle,
ReadOnly: true,
SubPath: "ca-bundle.crt",
})
podSpec.Containers[i].VolumeMounts = append(podSpec.Containers[i].VolumeMounts, caMount)
}
for i := range podSpec.InitContainers {
podSpec.InitContainers[i].VolumeMounts = append(podSpec.InitContainers[i].VolumeMounts, caMount)
}

log.FromContext(feast.Handler.Context).Info("Mounted custom CA bundle ConfigMap to Feast pods.")
Expand Down
26 changes: 26 additions & 0 deletions infra/feast-operator/internal/controller/services/tls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,19 @@ var _ = Describe("TLS Config", func() {
Expect(feastDeploy.Spec.Template.Spec.Containers[3].Command).To(ContainElements(ContainSubstring("--key")))
Expect(feastDeploy.Spec.Template.Spec.Volumes).To(HaveLen(5))

// verify init containers receive TLS volume mounts when all services have TLS
for _, initContainer := range feastDeploy.Spec.Template.Spec.InitContainers {
Expect(initContainer.VolumeMounts).To(ContainElement(
HaveField("MountPath", GetTlsPath(RegistryFeastType)),
), "init container %s should have registry TLS mount", initContainer.Name)
Expect(initContainer.VolumeMounts).To(ContainElement(
HaveField("MountPath", GetTlsPath(OnlineFeastType)),
), "init container %s should have online TLS mount", initContainer.Name)
Expect(initContainer.VolumeMounts).To(ContainElement(
HaveField("MountPath", GetTlsPath(OfflineFeastType)),
), "init container %s should have offline TLS mount", initContainer.Name)
}

// registry service w/ tls and in an openshift cluster
feast.Handler.FeatureStore = minimalFeatureStore()
feast.Handler.FeatureStore.Spec.Services = &feastdevv1.FeatureStoreServices{
Expand Down Expand Up @@ -336,6 +349,19 @@ var _ = Describe("TLS Config", func() {
Expect(GetUIContainer(*feastDeploy).Command).NotTo(ContainElements(ContainSubstring("--key")))
Expect(GetUIContainer(*feastDeploy).VolumeMounts).To(HaveLen(1))

// verify init containers receive only the offline TLS mount when only offline has TLS
for _, initContainer := range feastDeploy.Spec.Template.Spec.InitContainers {
Expect(initContainer.VolumeMounts).To(ContainElement(
HaveField("MountPath", GetTlsPath(OfflineFeastType)),
), "init container %s should have offline TLS mount", initContainer.Name)
Expect(initContainer.VolumeMounts).NotTo(ContainElement(
HaveField("MountPath", GetTlsPath(RegistryFeastType)),
), "init container %s should not have registry TLS mount when registry TLS is disabled", initContainer.Name)
Expect(initContainer.VolumeMounts).NotTo(ContainElement(
HaveField("MountPath", GetTlsPath(OnlineFeastType)),
), "init container %s should not have online TLS mount when online TLS is disabled", initContainer.Name)
}

// Test REST registry server TLS configuration
feast.Handler.FeatureStore = minimalFeatureStore()
restEnabled := true
Expand Down
Loading