-
Notifications
You must be signed in to change notification settings - Fork 14
[DLX] Integrate cache and watcher into dlx struct #76
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
[DLX] Integrate cache and watcher into dlx struct #76
Conversation
b167f5c
to
38a06d3
Compare
f2f3ca0
to
6163d52
Compare
6163d52
to
316dd12
Compare
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not adding kubeClientSet into options?
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pkg/kube/ingress.go
Outdated
type ingressValue struct { | ||
name string | ||
host string | ||
path string | ||
version string |
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's better to avoid changing the actual code for testing purposes, please remove this
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pkg/kube/ingress.go
Outdated
} | ||
|
||
ctx, cancel := context.WithCancel(dlxCtx) |
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.
ctx, cancel := context.WithCancel(dlxCtx) | |
contextWithCancel, cancel := context.WithCancel(dlxCtx) |
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
version can be passed as a parameter here, but let's not change the code
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.
pkg/dlx/dlx.go
Outdated
return &DLX{ | ||
logger: childLogger, | ||
handler: handler, | ||
server: &http.Server{ | ||
Addr: options.ListenAddress, | ||
}, | ||
cache: cache, |
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.
Why passing cache here if we already have it in watcher?
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed the cache from the DLX struct - CR comments - remove the cache from the DLX struct
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.
@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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.Get
to the handler - Single Responsibility Principle- the watcher is the responsible for the cache
- Single source of truth- the cache inside the watcher
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's why this log is DEBUG
=]
But ok I will remove
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.
…s to code structs and remove flooding log
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.
Very well!
Just a minor comment, but anyway approved ✅
cmd/dlx/app/dlx.go
Outdated
@@ -82,12 +82,12 @@ func Run(kubeconfigPath string, | |||
return errors.Wrap(err, "Failed to get client configuration") | |||
} | |||
|
|||
kubeClientSet, err := kubernetes.NewForConfig(restConfig) | |||
dlxOptions.KubeClientSet, err = kubernetes.NewForConfig(restConfig) |
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.
if dlxOptions.KubeClientSet, err = kubernetes.NewForConfig(restConfig); err != nil {}
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.
Fixed here - CR comment - proper err handling
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the json:"-"
needed?
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.
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 <[email protected]>
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.IngressHostCache
andkube.IngressWatcher
into the DLX.UpdateHandler
by adding a fast exit when the old and new objects share the sameResourceVersion
References
Additional Notes
requestHandler
as part of completing the full solution