[DLX] Integrate cache and watcher into dlx struct#76
Conversation
b167f5c to
38a06d3
Compare
f2f3ca0 to
6163d52
Compare
6163d52 to
316dd12
Compare
rokatyy
left a comment
There was a problem hiding this comment.
Nice! we are getting closer and closer to the final :)
Some questions and suggestions
cmd/dlx/app/dlx.go
Outdated
| func createDLX( | ||
| resourceScaler scalertypes.ResourceScaler, | ||
| options scalertypes.DLXOptions, | ||
| kubeClientSet kubernetes.Interface, |
There was a problem hiding this comment.
why not adding kubeClientSet into options?
There was a problem hiding this comment.
I initially assumed that options was meant only for static variables or configuration values.
From your comment, I understand that including runtime dependencies like the Kubernetes client in options is acceptable here—and likely the better practice in this case.
There was a problem hiding this comment.
pkg/kube/ingress.go
Outdated
| name string | ||
| host string | ||
| path string | ||
| version string |
There was a problem hiding this comment.
It's better to avoid changing the actual code for testing purposes, please remove this
There was a problem hiding this comment.
During development, I initially planned to use this in the actual code (e.g., for logging), but it turned out to be redundant—removing it.
There was a problem hiding this comment.
pkg/kube/ingress.go
Outdated
| resyncInterval = scalertypes.Duration{Duration: scalertypes.DefaultResyncInterval} | ||
| } | ||
|
|
||
| ctx, cancel := context.WithCancel(dlxCtx) |
There was a problem hiding this comment.
| ctx, cancel := context.WithCancel(dlxCtx) | |
| contextWithCancel, cancel := context.WithCancel(dlxCtx) |
There was a problem hiding this comment.
pkg/kube/ingress_test.go
Outdated
| testOldObj := suite.createDummyIngress(testCase.testOldObj.host, testCase.testOldObj.path, testCase.testOldObj.version, testCase.testOldObj.targets) | ||
| testNewObj := suite.createDummyIngress(testCase.testNewObj.host, testCase.testNewObj.path, testCase.testNewObj.version, testCase.testNewObj.targets) |
There was a problem hiding this comment.
version can be passed as a parameter here, but let's not change the code
There was a problem hiding this comment.
pkg/dlx/dlx.go
Outdated
| server: &http.Server{ | ||
| Addr: options.ListenAddress, | ||
| }, | ||
| cache: cache, |
There was a problem hiding this comment.
Why passing cache here if we already have it in watcher?
There was a problem hiding this comment.
The watcher updates the cache, but the DLX itself will need to read from it when receiving a request.
There was a problem hiding this comment.
@TomerShor but we can get it by dlx.watcher.cache, I think here it's better to stick with a single ownership and single source of truth (can also be done via encapsulation like watcher.Cache() or something if we need to only read it)
There was a problem hiding this comment.
Technically, we could access the cache via watcher.cache, but I think it's cleaner to initialize the cache in the main function and pass it explicitly to both the watcher and (in the upcoming PR) to the request handler.
This approach promotes loose coupling — the request handler and the watcher both interact with the cache, but neither should own it. More importantly, it’s preferable that the request handler remains unaware of the watcher operator to maintain a clear separation of concerns. This approach helps minimize unnecessary dependencies between components.
Since the cache is fully wired during initialization, having it stored on the DLX struct may be redundant — I’ll consider removing it if it’s not directly needed there.
Let me know what you think =]
There was a problem hiding this comment.
I removed the cache from the DLX struct - CR comments - remove the cache from the DLX struct
There was a problem hiding this comment.
@weilerN Watcher writes to the cache, and DLX reads it. Since watcher is a part of DLX, the idea of initializing and passing the cache separately to both contradicts the Single Responsibility Principle. Only the watcher should have the ability to modify the cache; DLX should not — it merely reads from it. Allowing both components to be independently wired with the same cache introduces unnecessary risk.
This approach helps minimize unnecessary dependencies between components.
The question here is: why do we want to minimize dependencies in this specific case? In fact, the dependency between DLX and watcher isn't unnecessary — it's inherent. DLX already owns the watcher; reading data from a cache managed by its child is a natural and clear dependency.
Trying to artificially decouple them reduce "visible" coupling, it weakens architectural clarity. It also opens the door to subtle bugs — for example, if someone mistakenly passes a copy or separate instance of the cache, we’ll end up with divergence that’s hard to track. This kind of split ownership violates the principle of having a single source of truth.
There was a problem hiding this comment.
@rokatyy Just to clarify — it’s the request handler, not the DLX itself, that reads from the cache. So either we pass the cache directly to the handler, or we pass the watcher and have the handler access the cache through the watcher.
If the concern is about restricting write access, exposing the full cache still leaves that possibility open. We’d need to either wrap it in the watcher (i.e. - watcher.ResolveFromCache() or something like that).
Since your main concern is ownership and making that responsibility explicit within the handler’s boundaries, I’ll follow your direction.
Let me know which approach you prefer ( watcher.cache.Get() or watcher.ResolveFromCache()), and I’ll update the code accordingly.
There was a problem hiding this comment.
@rokatyy @TomerShor I did some changes here (CR comment - move the cache into the watcher and expose only the cach…) to address all the points discussed in this thread:
- Exposing only the
cache.Getto the handler - Single Responsibility Principle- the watcher is the responsible for the cache
- Single source of truth- the cache inside the watcher
TomerShor
left a comment
There was a problem hiding this comment.
Minor comment. Overall looks great!
pkg/kube/ingress.go
Outdated
| iw.logger.DebugWith("No changes in resource, skipping", | ||
| "resourceVersion", oldIngressResource.ResourceVersion, | ||
| "ingressName", oldIngressResource.Name) |
There was a problem hiding this comment.
This will be super spammy - most of the time we won't have changes in the ingress, and with a small resync interval will get 1 log line for every nuclio ingress - could be a very large number.
No need for log here at all.
There was a problem hiding this comment.
That's why this log is DEBUG =]
But ok I will remove
There was a problem hiding this comment.
…s to code structs and remove flooding log
rokatyy
left a comment
There was a problem hiding this comment.
Very well!
Just a minor comment, but anyway approved ✅
cmd/dlx/app/dlx.go
Outdated
| } | ||
|
|
||
| kubeClientSet, err := kubernetes.NewForConfig(restConfig) | ||
| dlxOptions.KubeClientSet, err = kubernetes.NewForConfig(restConfig) |
There was a problem hiding this comment.
if dlxOptions.KubeClientSet, err = kubernetes.NewForConfig(restConfig); err != nil {}
There was a problem hiding this comment.
Fixed here - CR comment - proper err handling
rokatyy
left a comment
There was a problem hiding this comment.
Great idea with a nested reader interface!
44134be to
659d388
Compare
| ResolveTargetsFromIngressCallback ResolveTargetsFromIngressCallback `json:"-"` | ||
| ResyncInterval Duration | ||
| KubeClientSet kubernetes.Interface `json:"-"` |
There was a problem hiding this comment.
Why is the json:"-" needed?
There was a problem hiding this comment.
When starting the DLX, an error log appears indicating that the kubeClient failed to be marshaled.
Adding this annotation resolves this false-positive error.
### Description This PR integrates the cache into the handler in order to resolve the request target based on the new cache. ### Changes Made - Add the `IngressHostCacheReader` (the cache read-only access) to the handler - Add the logic for the new resolving (i.e. - try to resolve based on the cache, and if failed - resolve by the existing headers' logic) ### Testing - Added a flow test - for the entire request handling - Added unit tests for the resolving per case (i.e. - based on headers and based on ingress) ### References - Jira ticket link: https://iguazio.atlassian.net/browse/NUC-523 - External link: this PR is based on this branch - #76 ### Additional Notes - I left a **TODO** question inside the test file — it seems that when parsing the `targetURL`, the handler appends the path twice. This fix is relatively minor and has been tested manually, but since the current code still works, it's worth deciding whether we want to address it now.
### Description This PR integrates the new DLX from the Scaler package, which introduces a revised internal flow. The changes align the DLX initialization with the new Scaler initialization and configuration patterns by updating the `dlxOptions` and related logic. ### Changes Made - Extracted hardcoded configuration variables - Implemented the `scalertype.ResolveTargetsFromIngressCallback` for extracting Nuclio targets - Added changes to the DLX init (as described in the Jira) ### Testing - Unit tests for the new `resolveCallback` - Manual integration tests to verify the functionalities ### References - Jira ticket link - https://iguazio.atlassian.net/browse/NUC-509 - External links - the related scaler PRs -- v3io/scaler#76 -- v3io/scaler#78 ### Additional Notes - Before merging this PR, the `go.mod` must be modified since it currently relies on a feature branch --------- Co-authored-by: Katerina Molchanova <35141662+rokatyy@users.noreply.github.com>
Description
This PR integrates the cache and watcher components into the DLX to enable scale-from-zero functionality based on ingress resources rather than relying on request headers.
Changes Made
ingresscache.IngressHostCacheandkube.IngressWatcherinto the DLX.UpdateHandlerby adding a fast exit when the old and new objects share the sameResourceVersionReferences
Additional Notes
requestHandleras part of completing the full solution