Skip to content

feat: Allow to set registry server rest/grpc mode in operator#5364

Merged
tchughesiv merged 18 commits into
feast-dev:masterfrom
ntkathole:move_ui_rest
Jun 13, 2025
Merged

feat: Allow to set registry server rest/grpc mode in operator#5364
tchughesiv merged 18 commits into
feast-dev:masterfrom
ntkathole:move_ui_rest

Conversation

@ntkathole

@ntkathole ntkathole commented May 17, 2025

Copy link
Copy Markdown
Member

What this PR does / why we need it:

This PR adds capability in feast-operator to allow user to deploy registry serving in REST or gRPC or both mode.

New configuration fields:

  • GRPC: Toggle gRPC server (--grpc/--no-grpc)

  • RestAPI: Toggle REST API server (--rest-api)

  • When REST API is enabled:

    • Creates separate service with -rest suffix
    • Configures dedicated REST API port
    • Supports both HTTP/HTTPS
  • Service hostnames:

    • Separate hostname for REST API endpoints
    • Format: {service-name}-rest.{namespace}.svc.cluster.local:{port}
  • Maintains backward compatibility with existing gRPC deployments.

Setting restAPI: true will deploy registry with :
feast serve_registry --rest-api

spec:
  feastProject: example
  services:
    registry:
      local:
        server:
          restAPI: true
          grpc: true

Additionally, also fixed the issue in f231f0d with rest port not used when grpc is disabled and only rest-api is enabled. Earlier it was using default grpc port instead of default rest port.

@ntkathole ntkathole requested a review from a team as a code owner May 17, 2025 17:07
Comment thread infra/feast-operator/api/v1alpha1/featurestore_types.go Outdated

@tchughesiv tchughesiv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when rest is enabled for registry, does the default port change?

@ntkathole ntkathole force-pushed the move_ui_rest branch 3 times, most recently from 33d6450 to 1108633 Compare May 18, 2025 05:32
@ntkathole

Copy link
Copy Markdown
Member Author

when rest is enabled for registry, does the default port change?

nope, same default port is used, unless changed explicitly by user.

@tchughesiv tchughesiv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ntkathole please add unit tests to check that the rest option is being properly handled in the resulting Deployment object... and any other tests related to this PR that you think would be valuable. Also, please update the description to match any design changes that have occurred.

Comment thread infra/feast-operator/api/v1alpha1/featurestore_types.go Outdated
Comment thread infra/feast-operator/internal/controller/services/services.go Outdated
Comment thread infra/feast-operator/internal/controller/services/services.go Outdated

@tchughesiv tchughesiv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the failing e2e test around remote registry use, it's clear that using rest api mode will break registry's ability to be used as a "remote" provider type. this adds additional complexity that will need to be handled/checked ... likely in this method -

func (feast *FeastServices) setRemoteRegistryURL() error {
if feast.isRemoteHostnameRegistry() {
feast.Handler.FeatureStore.Status.ServiceHostnames.Registry = *feast.Handler.FeatureStore.Status.Applied.Services.Registry.Remote.Hostname
} else if feast.IsRemoteRefRegistry() {
remoteFeast, err := feast.getRemoteRegistryFeastHandler()
if err != nil {
return err
}
// referenced/remote registry must use the local registry server option and be in a 'Ready' state.
if remoteFeast != nil &&
remoteFeast.isRegistryServer() &&
apimeta.IsStatusConditionTrue(remoteFeast.Handler.FeatureStore.Status.Conditions, feastdevv1alpha1.RegistryReadyType) &&
len(remoteFeast.Handler.FeatureStore.Status.ServiceHostnames.Registry) > 0 {
feast.Handler.FeatureStore.Status.ServiceHostnames.Registry = remoteFeast.Handler.FeatureStore.Status.ServiceHostnames.Registry
} else {
return errors.New("Remote feast registry of referenced FeatureStore '" + remoteFeast.Handler.FeatureStore.Name + "' is not ready")
}
}
return nil
}

If restAPI is true in the remote registry CR being used, we'd want to notify the user in the Status that remote provider registry capabilities cannot be used.

It would probably be good idea to check this in with a unit test as well. Maybe ensure whatever status message you set is being handled correctly.

There are existing remote registry tests in infra/feast-operator/internal/controller/featurestore_controller_test.go for reference. Might be a good place to add more.

@ntkathole ntkathole force-pushed the move_ui_rest branch 5 times, most recently from ea9f800 to 75dcc2d Compare May 21, 2025 11:06
Comment thread infra/feast-operator/internal/controller/featurestore_controller_test.go Outdated
@ntkathole ntkathole force-pushed the move_ui_rest branch 3 times, most recently from d42d9e5 to 4953754 Compare May 22, 2025 04:32
@ntkathole ntkathole requested a review from tchughesiv May 22, 2025 05:53

@tchughesiv tchughesiv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking good, but we still need checks added as mentioned in this prior review comment -
#5364 (review)

@ntkathole

Copy link
Copy Markdown
Member Author

looking good, but we still need checks added as mentioned in this prior review comment - #5364 (review)

Can you provide an example here, I didn't get how user can set restAPI true in the remote registry ? I think we have only added restAPI flag option for local registry server configs.

@tchughesiv

Copy link
Copy Markdown
Contributor

A user can reference a registry in another FeatureStore CR and use it as a remote registry -

registry:
remote:
feastRef:
name: simple-feast-setup
namespace: default

We already have tests in place (both e2e & unit) around this feature. Given the restAPI breaks this functionality, you'll need to add an additional check in -

func (feast *FeastServices) setRemoteRegistryURL() error {
if feast.isRemoteHostnameRegistry() {
feast.Handler.FeatureStore.Status.ServiceHostnames.Registry = *feast.Handler.FeatureStore.Status.Applied.Services.Registry.Remote.Hostname
} else if feast.IsRemoteRefRegistry() {
remoteFeast, err := feast.getRemoteRegistryFeastHandler()
if err != nil {
return err
}
// referenced/remote registry must use the local registry server option and be in a 'Ready' state.
if remoteFeast != nil &&
remoteFeast.isRegistryServer() &&
apimeta.IsStatusConditionTrue(remoteFeast.Handler.FeatureStore.Status.Conditions, feastdevv1alpha1.RegistryReadyType) &&
len(remoteFeast.Handler.FeatureStore.Status.ServiceHostnames.Registry) > 0 {
feast.Handler.FeatureStore.Status.ServiceHostnames.Registry = remoteFeast.Handler.FeatureStore.Status.ServiceHostnames.Registry
} else {
return errors.New("Remote feast registry of referenced FeatureStore '" + remoteFeast.Handler.FeatureStore.Name + "' is not ready")
}
}
return nil
}

and unit test(s) verifying the new check.. e.g. -

cond := apimeta.FindStatusCondition(resource.Status.Conditions, feastdevv1alpha1.ReadyType)
Expect(cond).NotTo(BeNil())
Expect(cond.Message).To(Equal("Error: FeatureStore '" + name + "' can't reference itself in `spec.services.registry.remote.feastRef`"))
resource.Spec.Services.Registry.Remote.FeastRef.Name = "wrong"
err = k8sClient.Update(ctx, resource)
Expect(err).NotTo(HaveOccurred())
_, err = controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: nsName,
})
Expect(err).To(HaveOccurred())
err = k8sClient.Get(ctx, nsName, resource)
Expect(err).NotTo(HaveOccurred())
Expect(apimeta.FindStatusCondition(resource.Status.Conditions, feastdevv1alpha1.AuthorizationReadyType)).To(BeNil())
Expect(apimeta.FindStatusCondition(resource.Status.Conditions, feastdevv1alpha1.RegistryReadyType)).To(BeNil())
Expect(apimeta.IsStatusConditionTrue(resource.Status.Conditions, feastdevv1alpha1.ReadyType)).To(BeFalse())
cond = apimeta.FindStatusCondition(resource.Status.Conditions, feastdevv1alpha1.ReadyType)
Expect(cond).NotTo(BeNil())
Expect(cond.Message).To(Equal("Error: Referenced FeatureStore '" + resource.Spec.Services.Registry.Remote.FeastRef.Name + "' was not found"))

Signed-off-by: ntkathole <nikhilkathole2683@gmail.com>
@ntkathole

Copy link
Copy Markdown
Member Author

A user can reference a registry in another FeatureStore CR and use it as a remote registry -

registry:
remote:
feastRef:
name: simple-feast-setup
namespace: default

We already have tests in place (both e2e & unit) around this feature. Given the restAPI breaks this functionality, you'll need to add an additional check in -

func (feast *FeastServices) setRemoteRegistryURL() error {
if feast.isRemoteHostnameRegistry() {
feast.Handler.FeatureStore.Status.ServiceHostnames.Registry = *feast.Handler.FeatureStore.Status.Applied.Services.Registry.Remote.Hostname
} else if feast.IsRemoteRefRegistry() {
remoteFeast, err := feast.getRemoteRegistryFeastHandler()
if err != nil {
return err
}
// referenced/remote registry must use the local registry server option and be in a 'Ready' state.
if remoteFeast != nil &&
remoteFeast.isRegistryServer() &&
apimeta.IsStatusConditionTrue(remoteFeast.Handler.FeatureStore.Status.Conditions, feastdevv1alpha1.RegistryReadyType) &&
len(remoteFeast.Handler.FeatureStore.Status.ServiceHostnames.Registry) > 0 {
feast.Handler.FeatureStore.Status.ServiceHostnames.Registry = remoteFeast.Handler.FeatureStore.Status.ServiceHostnames.Registry
} else {
return errors.New("Remote feast registry of referenced FeatureStore '" + remoteFeast.Handler.FeatureStore.Name + "' is not ready")
}
}
return nil
}

and unit test(s) verifying the new check.. e.g. -

cond := apimeta.FindStatusCondition(resource.Status.Conditions, feastdevv1alpha1.ReadyType)
Expect(cond).NotTo(BeNil())
Expect(cond.Message).To(Equal("Error: FeatureStore '" + name + "' can't reference itself in `spec.services.registry.remote.feastRef`"))
resource.Spec.Services.Registry.Remote.FeastRef.Name = "wrong"
err = k8sClient.Update(ctx, resource)
Expect(err).NotTo(HaveOccurred())
_, err = controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: nsName,
})
Expect(err).To(HaveOccurred())
err = k8sClient.Get(ctx, nsName, resource)
Expect(err).NotTo(HaveOccurred())
Expect(apimeta.FindStatusCondition(resource.Status.Conditions, feastdevv1alpha1.AuthorizationReadyType)).To(BeNil())
Expect(apimeta.FindStatusCondition(resource.Status.Conditions, feastdevv1alpha1.RegistryReadyType)).To(BeNil())
Expect(apimeta.IsStatusConditionTrue(resource.Status.Conditions, feastdevv1alpha1.ReadyType)).To(BeFalse())
cond = apimeta.FindStatusCondition(resource.Status.Conditions, feastdevv1alpha1.ReadyType)
Expect(cond).NotTo(BeNil())
Expect(cond.Message).To(Equal("Error: Referenced FeatureStore '" + resource.Spec.Services.Registry.Remote.FeastRef.Name + "' was not found"))

This is handled in b991093 Thank you.

Comment thread infra/feast-operator/internal/controller/services/services.go Outdated
Comment thread infra/feast-operator/internal/controller/services/services.go Outdated
Comment thread infra/feast-operator/internal/controller/services/services.go Outdated
Comment thread infra/feast-operator/internal/controller/services/services.go
Comment thread infra/feast-operator/internal/controller/services/services.go Outdated
Comment thread infra/feast-operator/internal/controller/services/services.go Outdated
Comment thread infra/feast-operator/internal/controller/services/util.go Outdated
@tchughesiv

Copy link
Copy Markdown
Contributor

Looking good, a few comments.

ntkathole and others added 3 commits June 11, 2025 20:55
Co-authored-by: Tommy Hughes IV <tchughesiv@gmail.com>
Signed-off-by: ntkathole <nikhilkathole2683@gmail.com>
Signed-off-by: ntkathole <nikhilkathole2683@gmail.com>
Signed-off-by: ntkathole <nikhilkathole2683@gmail.com>

@tchughesiv tchughesiv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very close now, just a couple nits

Comment thread infra/feast-operator/internal/controller/services/services.go Outdated
Comment thread infra/feast-operator/internal/controller/services/services.go Outdated
Signed-off-by: ntkathole <nikhilkathole2683@gmail.com>
Signed-off-by: ntkathole <nikhilkathole2683@gmail.com>
Signed-off-by: ntkathole <nikhilkathole2683@gmail.com>
Comment thread sdk/python/feast/cli/serve.py
Comment thread infra/feast-operator/internal/controller/services/services.go Outdated
Comment thread infra/feast-operator/internal/controller/services/services.go Outdated
Comment thread infra/feast-operator/internal/controller/services/services.go
Comment thread infra/feast-operator/internal/controller/services/services.go
Comment thread infra/feast-operator/internal/controller/services/services.go Outdated
Comment thread infra/feast-operator/internal/controller/services/services.go Outdated
Comment thread infra/feast-operator/internal/controller/services/services.go
Comment thread infra/feast-operator/api/v1alpha1/featurestore_types.go Outdated

@tchughesiv tchughesiv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this whole block needs to change -

isRegistry := feastType == RegistryFeastType && feast.isRegistryServer()
grpcEnabled := !feast.isRegistryServer() || feast.isRegistryGrpcEnabled()
if grpcEnabled {
container.Ports = append(container.Ports, corev1.ContainerPort{
Name: name,
ContainerPort: getTargetPort(feastType, tls),
Protocol: corev1.ProtocolTCP,
})
}
if isRegistry && feast.isRegistryRestEnabled() {
container.Ports = append(container.Ports, corev1.ContainerPort{
Name: name + "-rest",
ContainerPort: getTargetRestPort(feastType, tls),
Protocol: corev1.ProtocolTCP,
})
}

the issue is that you're breaking the setting of ports for other service types (online, offline, etc)...
something like this should do the trick -

	if feastType == RegistryFeastType {
		if feast.isRegistryGrpcEnabled() {
			container.Ports = append(container.Ports, corev1.ContainerPort{
			    	Name:          name,
			    	ContainerPort: getTargetPort(feastType, tls),
			    	Protocol:      corev1.ProtocolTCP,
			})
		    }
		if feast.isRegistryRestEnabled() {
		    	container.Ports = append(container.Ports, corev1.ContainerPort{
		    		Name:          name + "-rest",
		    		ContainerPort: getTargetRestPort(feastType, tls),
		    		Protocol:      corev1.ProtocolTCP,
		    	})
		    }
        } else {
            container.Ports = append(container.Ports, corev1.ContainerPort{
		    	Name:          name,
		    	ContainerPort: getTargetPort(feastType, tls),
		    	Protocol:      corev1.ProtocolTCP,
            })
        }

the above would be done in addition to the other suggestions i've made around the isRegistryRestEnabled & isRegistryGrpcEnabled method changes

Signed-off-by: ntkathole <nikhilkathole2683@gmail.com>

@tchughesiv tchughesiv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking good! one more change requested.

we also need unit tests for changes made around the rest & grpc container ports, services, probeHandlers, etc.

Comment thread infra/feast-operator/api/v1alpha1/featurestore_types.go
Signed-off-by: ntkathole <nikhilkathole2683@gmail.com>
Comment thread infra/feast-operator/internal/controller/services/services.go
Comment thread infra/feast-operator/internal/controller/services/services.go
Comment thread infra/feast-operator/test/api/featurestore_types_test.go Outdated
Signed-off-by: ntkathole <nikhilkathole2683@gmail.com>
Signed-off-by: ntkathole <nikhilkathole2683@gmail.com>

@tchughesiv tchughesiv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@tchughesiv tchughesiv merged commit 99afd6d into feast-dev:master Jun 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants