ROX-12943: Sensor sets NodeID in NodeInventory#4484
Conversation
|
Skipping CI for Draft Pull Request. |
|
Current dependencies on/for this PR:
This comment was auto-generated by Graphite. |
|
Images are ready for the commit at 727b9b2. To use with deploy scripts, first |
|
/test all |
|
@vikin91: No presubmit jobs available for stackrox/stackrox@piotr/ROX-13722-readd-node-id DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
24d29ad to
90c8da2
Compare
|
/test all |
lvalerom
left a comment
There was a problem hiding this comment.
Just commenting because I haven't reviewed the entire PR yet.
e776a7c to
f74cf0b
Compare
|
/test gke-qa-e2e-tests gke-postgres-nongroovy-e2e-tests |
fredrb
left a comment
There was a problem hiding this comment.
Some comments that we can discuss synchronously.
| @@ -0,0 +1,51 @@ | |||
| package mocks | |||
There was a problem hiding this comment.
Why isn't this generated from the store interface using
//go:generate mockgen-wrapper
There was a problem hiding this comment.
If simple impl is enough then implement it in sensor/common/compliance/node_matcher_test.go
If it is more complex, then use mockgen.
In both cases remove AddOrUpdateNode from the interface.
|
|
||
| // nodeStore provides functionality to get nodes | ||
| type nodeStore interface { | ||
| AddOrUpdateNode(node *store.NodeWrap) bool |
There was a problem hiding this comment.
This doesn't seem to be used by production code, only for tests. Why does it need to be part of the interface?
sensor/common/store/nodes.go
Outdated
| ) | ||
|
|
||
| // NodeWrap adds address information to nodes to forward to central | ||
| type NodeWrap struct { |
There was a problem hiding this comment.
I don't understand the design decision of exposing this internal struct of the listeners rather than using a separate interface returning only *storage.Node, which is what is really used outside the listener.
I.e.: I don't see Addresses being used anywhere other than in the listener. So why do they need to be exposed in sensor/common?
Most stores have the similar requirements. The way this is handled for other stores is to have a sensor-wide API for the store (defined in sensor/common), which exposes storage.* type structs. And have its implementation be internal to sensor/kubernetes, where you can use any additional information needed.
Take deployments, for example:
// In sensor/common/store:
type DeploymentStore interface {
Get(id string) *storage.Deployment
}
// In sensor/kubernetes/listener/resources
type DeploymentStore struct { ... }
func (ds *DeploymentStore) addOrUpdateDeployment(wrap *deploymentWrap) { ... }
// Get returns deployment for supplied id.
func (ds *DeploymentStore) Get(id string) *storage.Deployment {
wrap := ds.getWrap(id)
// parse to a storage.Deployment type to comply with public common interface
return wrap.GetDeployment()
}| // nodeStore represents a collection of NodeWraps | ||
| type nodeStore interface { | ||
| AddOrUpdateNode(node *store.NodeWrap) bool | ||
| RemoveNode(node *storage.Node) | ||
| GetNode(nodeName string) *store.NodeWrap | ||
| GetNodes() []*store.NodeWrap | ||
| } |
There was a problem hiding this comment.
Should be in the common file with all other interfaces (only Get func).
The other functions can be in another interface that will be used in the listeners. Then the fucns can be private.
There was a problem hiding this comment.
Try to follow the example of Roles an RBACs (optional).
Moving the small interface to types.go should be enough.
|
I addressed most of the comments and stumbled upon an issue with code generation. I want the CI to give this a shot. Will ping you when the PR is ready to be looked at again. |
|
The CI failure for |
Unexport unnecessarily exported interface Remove not needed mock
d834beb to
f4d7882
Compare
|
I rebased again to fix the CI issues (see msg) |
lvalerom
left a comment
There was a problem hiding this comment.
Looks good to me. I have some comments but I don't want to block the PR since I think most of them can be skipped or done in follow-up PRs.
| } | ||
|
|
||
| // Nodes returns the Nodes public interface | ||
| func (p *InMemoryStoreProvider) Nodes() *nodeStoreImpl { |
There was a problem hiding this comment.
I know that this goes against the go convention of "Accept interfaces, return structs", but I would either change this to store.NodeStore or I would change the implementations above. Otherwise it looks like they are different when in reality they're meant to do the same.
There was a problem hiding this comment.
I see your point. Let me share mine.
There are three four options here:
func (p *InMemoryStoreProvider) Nodes() store.NodeStore
This is a problem cause we rely on the nodeStore interface in some places.
sensor/kubernetes/listener/resources/dispatcher.go:91:65: cannot use storeProvider.Nodes() (value of type store.NodeStore) as nodeStore value in argument to newNodeDispatcher: store.NodeStore does not implement nodeStore (missing method addOrUpdateNode) (typecheck)
func (p *InMemoryStoreProvider) Nodes() nodeStore
This is fine for now, but returning an unexported interface may limit the usability of this method in the future.
func (p *InMemoryStoreProvider) Nodes() *nodeStoreImpl
The current state. It allows to add checks for the impl to make sure that it implements both interfaces:
var _ nodeStore = (*nodeStoreImpl)(nil)
var _ store.NodeStore = (*nodeStoreImpl)(nil)func (p *InMemoryStoreProvider) Nodes() BigNodeStore
Create new interface BigNodeStore (name subject to change) that would do:
type BigNodeStore interface {
nodeStore
store.NodeStore
}Based on that, I think that option 3 looks the best. What do you think?
There was a problem hiding this comment.
I think there is also a 5th option where we make store.NodeStore a copy of nodeStore but the methods from nodeStore (addOrUpdateNode etc.) will be unexported in the new bigger store.NodeStore.
Edit: nah, this is impossible, because the impl would need to be in the same package as the interface and this is not the case for us.
For
type NodeStore interface {
GetNode(nodeName string) *storage.Node
addOrUpdateNode(node *storage.Node) bool
removeNode(node *storage.Node)
getNode(nodeName string) *storage.Node
getNodes() []*storage.Node
}I get
Cannot use 'storeProvider.Nodes()' (type store.NodeStore) as the type nodeStore Type cannot implement 'nodeStore' as it has a non-exported method and is defined in a different package
which in the end would force me to export the unexported methods and I assume we do not want it.
There was a problem hiding this comment.
Following the other alternative (returning impls everywhere) would look like this:
type InMemoryStoreProvider struct {
deploymentStore *DeploymentStore // Interface named the same as implementation - add Impl to type name?
podStore *PodStore
serviceStore *serviceStore
rbacStore rbac.storeImpl // Unexported - would need to export `storeImpl`
endpointManager *endpointManagerImpl
nodeStore *nodeStoreImpl
entityStore *clusterentities.Store // this is actually Impl - add Impl to type name?
}As it contains a lot of renaming I would like to do this in a followup PR. Would that be okay?
There was a problem hiding this comment.
I have a simple suggestion related to (1). The only place where it seems like you should be needing the private struct instead of the public interface is when instantiating the handlers. In sensor/kubernetes/listener/resources/dispatcher.go you could simply do:
nodeDispatcher: newNodeDispatcher(deploymentStore, storeProvider.nodeStore, endpointManager),And the dispatcher will be able to call the private store functions.
If I'm not mistaken, when you make this change, you should be able to switch the .Nodes() function to the public interface without any compiler errors.
This is what we do for RBACs and Services stores. Maybe it's not the ideal way of doing things and maybe you're on to something better. But I'd opt for the easiest thing here which is just passing the internal struct to the handler. We can have a sync discussion after this PR on how we could improve this if you'd like.
Could you give it a try?
There was a problem hiding this comment.
Add me to that sync discussion if possible 🙂 I have similar feelings as Fred but I feel they might not align with "the Go way".
There was a problem hiding this comment.
Here is it 727b9b2 - I cannot check it locally, but CI should do the job for us.
There was a problem hiding this comment.
Looks like it worked great! I will be merging this in a moment
fredrb
left a comment
There was a problem hiding this comment.
I'm fine with this once you address Luis' comment. Thanks for making the changes!
| } | ||
|
|
||
| // Nodes returns the Nodes public interface | ||
| func (p *InMemoryStoreProvider) Nodes() *nodeStoreImpl { |
Maddosaurus
left a comment
There was a problem hiding this comment.
Thanks for addressing the feedback from our sync review. Apart from the other ongoing discussions, I found a comment to fix, but that's non-blocking
Co-authored-by: Matthias <Maddosaurus@users.noreply.github.com>
| "github.com/stackrox/rox/generated/storage" | ||
| ) | ||
|
|
||
| type nodeStore interface { |
There was a problem hiding this comment.
I think this interface should go, right? Now that you've added the new interface in sensor/common/store this one doesn't need to exist.
There was a problem hiding this comment.
That is correct, I missed that one. Should be fixed now.
| } | ||
|
|
||
| // NewNodeIDMatcher creates a NodeIDMatcherImpl | ||
| func NewNodeIDMatcher(store nodeStore) *NodeIDMatcherImpl { |
There was a problem hiding this comment.
Use the public interface from sensor/common/store
jschnath
left a comment
There was a problem hiding this comment.
I have nothing to add that wasn't mentioned yet, LGTM
|
4 approvals and 100% green CI :) Looks like it is time to merge this 🚢 |

Description
This PR was created as a split from #4430 in order to make the change easier to review.
This PR is meant to replace #3892.
Here, we change Sensor to store the Nodes in the in-memory store, so that Sensor is able to find a Node object based on the node name that arrived from Compliance.
When NodeInventory arrives to Sensor from Compliance it contains only the node name. Next, Sensor searches the in-memory store to find the Node ID for a given name. If the ID is found, then
NodeInventory.NodeIDfield is set and NodeInventory is sent to Central. When Node cannot be found, the NodeInventory message is dropped.Checklist
Not apply
Testing Performed
Selected logs