Skip to content

[Framework] implement ingress operator#75

Merged
TomerShor merged 13 commits intov3io:developmentfrom
weilerN:nuc-498-implement-ingress-operator
Jul 8, 2025
Merged

[Framework] implement ingress operator#75
TomerShor merged 13 commits intov3io:developmentfrom
weilerN:nuc-498-implement-ingress-operator

Conversation

@weilerN
Copy link
Collaborator

@weilerN weilerN commented Jul 3, 2025

Description

Full description in the LLD that linked to this Jira - https://iguazio.atlassian.net/browse/NUC-498
This PR introduces a new IngressWatcher component that uses Kubernetes informers to watch for Ingress resource changes and updates an internal IngressHostCache accordingly. The watcher handles add, update, and delete events, extracts relevant data (host, path, target list), and reflects those changes in the in-memory cache.

Affected Areas

  • pkg/kube/ingress.go – new file with the IngressWatcher implementation
  • Uses IngressHostCache from pkg/ingresscache
  • Integrates with k8s.io/client-go for informers

Testing

  • Unit tests coverage for all event handler paths (add, update, delete) correctly modify the ingress cache.
  • Unit tests validates cache sync behavior and logging of edge cases (e.g., missing host/path).
  • Integrations tests are out of scope for this PR, will be introduced as part of the code changes in Nuclio

Changes Made

  • Added IngressWatcher struct to manage and respond to Kubernetes Ingress resource changes.
  • Implemented handlers for Add, Update, and Delete events.
  • Extracted ingress data: host, path, targets via helper methods.
  • Registered shared informer factory with label selector support.

Additional notes

  • Assumes each Ingress contains exactly one rule and one path.
  • Includes detailed error logging for resilience and observability.
  • Additional PR for integrating the ingress cache and ingress watcher is needed

@weilerN weilerN force-pushed the nuc-498-implement-ingress-operator branch from 629f57b to ad1e484 Compare July 3, 2025 16:18
@weilerN weilerN requested review from TomerShor and rokatyy July 3, 2025 16:26
@weilerN weilerN marked this pull request as ready for review July 3, 2025 16:27
Copy link
Collaborator

@rokatyy rokatyy left a comment

Choose a reason for hiding this comment

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

Some questions and suggestion to begin with. Also, please make sure that all errors start with capitalised letters

Comment on lines 145 to 155
func (iw *IngressWatcher) IngressHandlerDeleteFunc(obj interface{}) {
host, path, targets, err := iw.extractIngressValuesFromIngressResource(obj)
if err != nil {
iw.logger.WarnWith("Delete ingress handler failure- failed to extract values from object", "error", err)
return
}

if err = iw.ingressCache.Delete(host, path, targets); err != nil {
iw.logger.WarnWith("Delete ingress handler failure- failed delete from cache", "error", err, "object", obj)
return
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

please rename all this methods to not have ingress in them, it is already in the name of IngressWatcher, so it can be simply Add, Update, Delete

Copy link
Collaborator

Choose a reason for hiding this comment

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

Agreed, it can not have Func, e.g. AddHandler etc

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed here - CR comments

}

// extractHostPathTarget extracts the host, path, and targets from the ingress resource
func (iw *IngressWatcher) extractHostPathTarget(ingress *networkingv1.Ingress) (string, string, []string, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

How about a structure to return all these objects?

Suggested change
func (iw *IngressWatcher) extractHostPathTarget(ingress *networkingv1.Ingress) (string, string, []string, error) {
type IngressValues struct {
Host string
Path string
Targets []string
}

it will improve readability and get rid of many "" and etc

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed here - CR comments

return "", errors.New("no rules found in ingress")
}

// Ingress must contain exactly one rule
Copy link
Collaborator

Choose a reason for hiding this comment

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

It is not a rule in k8s, so if it contains more, we should still ignore that and get the first one, but add log about that

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed here - CR comments

Comment on lines 231 to 232
// Ingress must contain exactly one rule
httpPath := rule.HTTP.Paths[0]
Copy link
Collaborator

Choose a reason for hiding this comment

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

same here

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed here - CR comments

}

if err = iw.ingressCache.Set(newHost, newPath, newTargets); err != nil {
iw.logger.WarnWith("Update ingress handler failure- failed to add the new value", "error", err, "object", newObj)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
iw.logger.WarnWith("Update ingress handler failure- failed to add the new value", "error", err, "object", newObj)
iw.logger.WarnWith("Update ingress handler failure - failed to add the new value",
"error", err, "object", newObj)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed here - CR comments

// if the host or path has changed, we need to delete the old entry
if oldHost != newHost || oldPath != newPath {
if err = iw.ingressCache.Delete(oldHost, oldPath, oldTargets); err != nil {
iw.logger.WarnWith("Update ingress handler failure - failed to delete old ingress", "error", err)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
iw.logger.WarnWith("Update ingress handler failure - failed to delete old ingress", "error", err)
iw.logger.WarnWith("Update ingress handler failure - failed to delete old ingress",
"error", err)

and same in all the places above

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed here - CR comments

}

if err = iw.ingressCache.Set(host, path, targets); err != nil {
iw.logger.WarnWith("Add ingress handler failure- failed to add the new value", "error", err, "object", obj)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
iw.logger.WarnWith("Add ingress handler failure- failed to add the new value", "error", err, "object", obj)
iw.logger.WarnWith("Add ingress handler failure - failed to add the new value",
"error", err, "object", obj)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed here - CR comments

Comment on lines 213 to 221
func (iw *IngressWatcher) getPathFromIngress(ingress *networkingv1.Ingress) (string, error) {
if ingress == nil {
return "", errors.New("ingress is nil")
}

if len(ingress.Spec.Rules) == 0 {
return "", errors.New("no rules found in ingress")
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

duplication of the code in getHostFromIngress, can be moved to smth like

func (iw *IngressWatcher) getFirstRule(ingress *networkingv1.Ingress) (*networkingv1.IngressRule, error) {
	if ingress == nil {
		return nil, errors.New("ingress is nil")
	}

	if len(ingress.Spec.Rules) == 0 {
		return nil, errors.New("no rules found in ingress")
	}

	return &ingress.Spec.Rules[0], nil
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed here - CR comments

Comment on lines 187 to 191
targets, err := iw.resolveTargetsCallback(ingress)
if err != nil {
return "", "", nil, errors.Wrap(err, "Failed to extract targets from ingress labels")
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

what if resolveTargetsCallback does some additional filtering and returns empty targets if ingress doesn't meet the conditions, would it make sense to put it above getHostFromIngress?

Copy link
Collaborator Author

@weilerN weilerN Jul 6, 2025

Choose a reason for hiding this comment

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

  1. I agree that since the callback function is external to the scaler, it's appropriate to check for nil before using the returned targets.
  2. The order of the function calls isn't critical, but I originally structured them to follow the logical route hierarchy: host -> path -> targets.
    If I understood you correctly, you'd like the target resolution to come first since it’s the most complex part, and placing it earlier supports a fail-fast approach. That makes sense, and I’ll update the order accordingly.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed here - CR comments

"k8s.io/client-go/tools/cache"
)

type ResolveTargetsFromIngressCallback func(ingress *networkingv1.Ingress) ([]string, error)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please add a docstring for this type, as its function is expected to be implemented in a custom way. We should provide some guidance on what the implementation should include.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed here - CR comments

ctx context.Context,
dlxLogger logger.Logger,
kubeClient kubernetes.Interface,
ingressCache *ingresscache.IngressCache,
Copy link
Collaborator

Choose a reason for hiding this comment

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

In the struct above the property is defined as the IngressHostCache interface, but here you receive a pointer to the actual implementation IngressCache.
This should also be the interface.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed here - CR comments


ingressWatcher := &IngressWatcher{
ctx: ctx,
logger: dlxLogger,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
logger: dlxLogger,
logger: dlxLogger.GetChild("watcher"),

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed here - CR comments

) (*IngressWatcher, error) {
factory := informers.NewSharedInformerFactoryWithOptions(
kubeClient,
30*time.Second,
Copy link
Collaborator

Choose a reason for hiding this comment

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

This resync interval should be configurable

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed here - CR comments

}

func (iw *IngressWatcher) Start() error {
iw.logger.Debug("Starting IngressWatcher")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
iw.logger.Debug("Starting IngressWatcher")
iw.logger.Info("Starting ingress watcher")

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed here - CR comments

iw.factory.Start(iw.ctx.Done())

if !cache.WaitForCacheSync(iw.ctx.Done(), iw.informer.HasSynced) {
return errors.New("failed to sync ingress cache")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
return errors.New("failed to sync ingress cache")
return errors.New("Failed to sync ingress cache")

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed here - CR comments

Comment on lines 55 to 58
// TestIngressWatcherTestSuite runs the test suite
func TestIngressWatcherTestSuite(t *testing.T) {
suite.Run(t, new(IngressWatcherTestSuite))
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Move to the bottom of the file

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed here - CR comments

name string
testArgs ingressArgs
expectedResult []string
initialStateCache *ingressArgs
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
initialStateCache *ingressArgs
initialCachedData *ingressArgs

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed here - CR comments

Comment on lines 147 to 151
type ingressArgs struct {
host string
path string
targets []string
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Following @rokatyy 's suggestion, this can be a struct in ingress.go.
But generally - if you see that you defined the same struct multiple times - define it once outside of the tests.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed here - CR comments

for _, testCase := range []struct {
name string
expectedResults []expectedResults
initialStateCache *ingressArgs
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
initialStateCache *ingressArgs
initialCacheData *ingressArgs

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed here - CR comments

Comment on lines 286 to 292
type expectedResult struct {
host string
path string
targets []string
expectError bool
expectErrorMsg string
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same for this - move it to the top of the file.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed here - CR comments

@weilerN weilerN requested review from TomerShor and rokatyy July 6, 2025 16:04
Copy link
Collaborator

@rokatyy rokatyy left a comment

Choose a reason for hiding this comment

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

LGTM 👍 , just a minor thing regarding the comment

//
// Implementation guidelines:
// - Return a non-nil slice when targets are successfully resolved
// - Return a non-nil error if resolution fails or no targets are found
Copy link
Collaborator

Choose a reason for hiding this comment

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

why error when no targets found? maybe user code contain some custom filtering. I think that's ok not to throw error in this case, just ignore if empty

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed here - CR comments

@weilerN weilerN force-pushed the nuc-498-implement-ingress-operator branch from d00d4c6 to 0522781 Compare July 7, 2025 14:11
@weilerN weilerN requested a review from TomerShor July 7, 2025 14:21
Copy link
Collaborator

@TomerShor TomerShor left a comment

Choose a reason for hiding this comment

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

Last minor comments from me

@weilerN weilerN requested a review from TomerShor July 8, 2025 08:35
@TomerShor TomerShor merged commit 23d5465 into v3io:development Jul 8, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants