Skip to content

ROX-12943: Sensor sets NodeID in NodeInventory#4484

Merged
vikin91 merged 23 commits intomasterfrom
prjs/ROX-12943-sensor-maps-node-id
Jan 27, 2023
Merged

ROX-12943: Sensor sets NodeID in NodeInventory#4484
vikin91 merged 23 commits intomasterfrom
prjs/ROX-12943-sensor-maps-node-id

Conversation

@vikin91
Copy link
Contributor

@vikin91 vikin91 commented Jan 20, 2023

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.NodeID field is set and NodeInventory is sent to Central. When Node cannot be found, the NodeInventory message is dropped.

Checklist

  • Investigated and inspected CI test results
  • Unit test and regression tests added

Not apply

  • Evaluated and added CHANGELOG entry if required
  • Determined and documented upgrade steps
  • Documented user facing changes (create PR based on openshift/openshift-docs and merge into rhacs-docs)

Testing Performed

  • CI
  • Deploy locally on Colima, enable FF and check logs

Selected logs

# sensor
sensor-66b879fbd7-hlhvz common/compliance: 2023/01/20 10:47:25.681686 node_inventory_handler_impl.go:107: Info: Mapping NodeInventory name 'colima' to Node ID '252afb4d-0694-45c3-9f86-e1bd7d7a26de'

# central
central-c5dcb8cc8-cnvq5 sensor/service/pipeline/nodeinventory: 2023/01/20 10:47:55.691034 pipeline.go:52: Info: Central received NodeInventory for Node name='colima' ID='252afb4d-0694-45c3-9f86-e1bd7d7a26de'

@openshift-ci
Copy link

openshift-ci bot commented Jan 20, 2023

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@vikin91
Copy link
Contributor Author

vikin91 commented Jan 20, 2023

@ghost
Copy link

ghost commented Jan 20, 2023

Images are ready for the commit at 727b9b2.

To use with deploy scripts, first export MAIN_IMAGE_TAG=3.73.x-562-g727b9b258c.

@vikin91
Copy link
Contributor Author

vikin91 commented Jan 20, 2023

/test all

@openshift-ci
Copy link

openshift-ci bot commented Jan 20, 2023

@vikin91: No presubmit jobs available for stackrox/stackrox@piotr/ROX-13722-readd-node-id

Details

In response to this:

/test all

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.

Base automatically changed from piotr/ROX-13722-readd-node-id to master January 23, 2023 09:09
@vikin91 vikin91 force-pushed the prjs/ROX-12943-sensor-maps-node-id branch from 24d29ad to 90c8da2 Compare January 23, 2023 09:15
@vikin91 vikin91 marked this pull request as ready for review January 23, 2023 09:16
@vikin91
Copy link
Contributor Author

vikin91 commented Jan 23, 2023

/test all

Copy link
Contributor

@lvalerom lvalerom left a comment

Choose a reason for hiding this comment

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

Just commenting because I haven't reviewed the entire PR yet.

@vikin91 vikin91 force-pushed the prjs/ROX-12943-sensor-maps-node-id branch from e776a7c to f74cf0b Compare January 24, 2023 10:54
@vikin91
Copy link
Contributor Author

vikin91 commented Jan 24, 2023

/test gke-qa-e2e-tests gke-postgres-nongroovy-e2e-tests

Copy link
Contributor

@fredrb fredrb left a comment

Choose a reason for hiding this comment

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

Some comments that we can discuss synchronously.

@@ -0,0 +1,51 @@
package mocks
Copy link
Contributor

Choose a reason for hiding this comment

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

Why isn't this generated from the store interface using

//go:generate mockgen-wrapper

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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
Copy link
Contributor

Choose a reason for hiding this comment

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

This doesn't seem to be used by production code, only for tests. Why does it need to be part of the interface?

)

// NodeWrap adds address information to nodes to forward to central
type NodeWrap struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

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()
}

Comment on lines 14 to 17
// 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
}
Copy link
Contributor Author

@vikin91 vikin91 Jan 25, 2023

Choose a reason for hiding this comment

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

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.

Copy link
Contributor Author

@vikin91 vikin91 Jan 25, 2023

Choose a reason for hiding this comment

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

Try to follow the example of Roles an RBACs (optional).

Moving the small interface to types.go should be enough.

@vikin91
Copy link
Contributor Author

vikin91 commented Jan 25, 2023

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.

@vikin91
Copy link
Contributor Author

vikin91 commented Jan 25, 2023

The CI failure for gke-postgres-nongroovy-e2e-tests affects also master, so not a blocker here.

@vikin91 vikin91 requested a review from fredrb January 25, 2023 16:16
@vikin91 vikin91 force-pushed the prjs/ROX-12943-sensor-maps-node-id branch from d834beb to f4d7882 Compare January 26, 2023 08:49
@vikin91
Copy link
Contributor Author

vikin91 commented Jan 26, 2023

I rebased again to fix the CI issues (see msg)

Copy link
Contributor

@lvalerom lvalerom left a comment

Choose a reason for hiding this comment

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

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 {
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

+1 to this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see your point. Let me share mine.

There are three four options here:

  1. 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)
  1. 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.

  1. 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)
  1. 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?

Copy link
Contributor Author

@vikin91 vikin91 Jan 26, 2023

Choose a reason for hiding this comment

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

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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?

Copy link
Contributor

Choose a reason for hiding this comment

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

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?

Copy link
Contributor

Choose a reason for hiding this comment

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

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".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here is it 727b9b2 - I cannot check it locally, but CI should do the job for us.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looks like it worked great! I will be merging this in a moment

Copy link
Contributor

@fredrb fredrb left a comment

Choose a reason for hiding this comment

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

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 {
Copy link
Contributor

Choose a reason for hiding this comment

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

+1 to this

Copy link
Contributor

@Maddosaurus Maddosaurus left a comment

Choose a reason for hiding this comment

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

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 {
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That is correct, I missed that one. Should be fixed now.

}

// NewNodeIDMatcher creates a NodeIDMatcherImpl
func NewNodeIDMatcher(store nodeStore) *NodeIDMatcherImpl {
Copy link
Contributor

Choose a reason for hiding this comment

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

Use the public interface from sensor/common/store

Copy link
Contributor

@jschnath jschnath left a comment

Choose a reason for hiding this comment

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

I have nothing to add that wasn't mentioned yet, LGTM

@vikin91
Copy link
Contributor Author

vikin91 commented Jan 27, 2023

4 approvals and 100% green CI :) Looks like it is time to merge this 🚢

@vikin91 vikin91 merged commit 909899e into master Jan 27, 2023
@vikin91 vikin91 deleted the prjs/ROX-12943-sensor-maps-node-id branch January 27, 2023 10:18
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.

5 participants