Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
90185b2
Provide imageScanServiceClient to clairify
vikin91 Jan 17, 2023
47418eb
Implement Sensor part from ROX-12943
vikin91 Jan 17, 2023
2c640ca
Make log messages more informative
vikin91 Jan 20, 2023
996abd1
Remove clairify part that should belong to another PR
vikin91 Jan 20, 2023
67df967
Fix style by adding comments to exported methods
vikin91 Jan 20, 2023
d07ac33
Simplify handling nodeInventory in nodeInventoryHandlerImpl
vikin91 Jan 20, 2023
b025225
Add test coverage for nodeInventoryHandlerImpl.findNodeID
vikin91 Jan 20, 2023
0370da4
Do not generate mocks for store.NodeStore
vikin91 Jan 20, 2023
ef0bcc0
Resolve conflicts between mocks
vikin91 Jan 20, 2023
cb9b884
Fix style issues
vikin91 Jan 20, 2023
dd86595
Readd accidentally deleted OnDeploymentCreateOrUpdateByID
vikin91 Jan 23, 2023
e35935e
Cleanup nodeDispatcher.ProcessEvent
vikin91 Jan 23, 2023
37a8473
Remove redundant parameter NodeStore
vikin91 Jan 24, 2023
84c3758
Refactor nodeStore interface
vikin91 Jan 24, 2023
f286fd1
Simplify NodeIdMatcher
vikin91 Jan 24, 2023
bc1c0a2
Rerun code generation
vikin91 Jan 24, 2023
fa71e38
Refactor nodeStore interfaces
vikin91 Jan 25, 2023
f4d7882
Fix comments
vikin91 Jan 25, 2023
48c2883
Add testcase for NodeInventoryHandler with nil input
vikin91 Jan 26, 2023
91c1ac1
Add testcase for NodeInventoryHandler when no node can be found
vikin91 Jan 26, 2023
64842db
Fix comment
vikin91 Jan 26, 2023
ddf0af3
Remove duplicate interface
vikin91 Jan 26, 2023
727b9b2
Make InMemoryStoreProvider.Nodes() return store.NodeStore
vikin91 Jan 26, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions central/sensor/service/pipeline/nodeinventory/pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ func (p *pipelineImpl) Run(ctx context.Context, clusterID string, msg *central.M
return errors.Errorf("unexpected resource type %T for node inventory", event.GetResource())
}

// TODO(ROX-12240, ROX-13053): Do something meaningful with the nodeInventory
log.Debugf("Central received NodeInventory: %+v", nodeInventory)
// TODO(ROX-12975): Handle nodeInventory
log.Infof("Central received NodeInventory for Node name='%s' ID='%s'", nodeInventory.GetNodeName(), nodeInventory.GetNodeId())

return nil
}
Expand Down
9 changes: 6 additions & 3 deletions sensor/common/compliance/node_inventory_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,21 @@ import (
"github.com/stackrox/rox/sensor/common"
)

// NodeInventoryHandler is responsible for handling arriving NodeInventory messages, processing them, and sending them to central
type NodeInventoryHandler interface {
// nodeInventoryHandler is responsible for handling arriving NodeInventory messages, processing them, and sending them to central
type nodeInventoryHandler interface {
common.SensorComponent
Stopped() concurrency.ReadOnlyErrorSignal
}

var _ nodeInventoryHandler = (*nodeInventoryHandlerImpl)(nil)

// NewNodeInventoryHandler returns a new instance of a NodeInventoryHandler
func NewNodeInventoryHandler(ch <-chan *storage.NodeInventory) NodeInventoryHandler {
func NewNodeInventoryHandler(ch <-chan *storage.NodeInventory, matcher NodeIDMatcher) *nodeInventoryHandlerImpl {
return &nodeInventoryHandlerImpl{
inventories: ch,
toCentral: nil,
lock: &sync.Mutex{},
stopper: concurrency.NewStopper(),
nodeMatcher: matcher,
}
}
19 changes: 15 additions & 4 deletions sensor/common/compliance/node_inventory_handler_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ var (
type nodeInventoryHandlerImpl struct {
inventories <-chan *storage.NodeInventory
toCentral <-chan *central.MsgFromSensor

nodeMatcher NodeIDMatcher
// lock prevents the race condition between Start() [writer] and ResponsesC() [reader]
lock *sync.Mutex
stopper concurrency.Stopper
Expand Down Expand Up @@ -77,15 +77,24 @@ func (c *nodeInventoryHandlerImpl) run() <-chan *central.MsgFromSensor {
c.stopper.Flow().StopWithError(errInputChanClosed)
return
}
// TODO(ROX-12943): Do something with the inventory, e.g., attach NodeID
c.sendInventory(toC, inventory)
if inventory == nil {
log.Warnf("Received nil NodeInventory - not sending node inventory to Central")
break
}
if nodeID, err := c.nodeMatcher.GetNodeID(inventory.GetNodeName()); err != nil {
log.Warnf("Node '%s' unknown to sensor - not sending node inventory to Central", inventory.GetNodeName())
} else {
inventory.NodeId = nodeID
log.Infof("Mapping NodeInventory name '%s' to Node ID '%s'", inventory.GetNodeName(), nodeID)
c.sendNodeInventory(toC, inventory)
}
}
}
}()
return toC
}

func (c *nodeInventoryHandlerImpl) sendInventory(toC chan *central.MsgFromSensor, inventory *storage.NodeInventory) {
func (c *nodeInventoryHandlerImpl) sendNodeInventory(toC chan<- *central.MsgFromSensor, inventory *storage.NodeInventory) {
if inventory == nil {
return
}
Expand All @@ -94,6 +103,8 @@ func (c *nodeInventoryHandlerImpl) sendInventory(toC chan *central.MsgFromSensor
case toC <- &central.MsgFromSensor{
Msg: &central.MsgFromSensor_Event{
Event: &central.SensorEvent{
Id: inventory.GetNodeId(),
Action: central.ResourceAction_UNSET_ACTION_RESOURCE, // There is no action required for NodeInventory as this is not a K8s resource
Resource: &central.SensorEvent_NodeInventory{
NodeInventory: inventory,
},
Expand Down
73 changes: 66 additions & 7 deletions sensor/common/compliance/node_inventory_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func (s *NodeInventoryHandlerTestSuite) TearDownTest() {
func (s *NodeInventoryHandlerTestSuite) TestResponsesCShouldPanicWhenNotStarted() {
inventories := make(chan *storage.NodeInventory)
defer close(inventories)
h := NewNodeInventoryHandler(inventories)
h := NewNodeInventoryHandler(inventories, &mockAlwaysHitNodeIDMatcher{})
s.Panics(func() {
h.ResponsesC()
})
Expand All @@ -76,7 +76,7 @@ func (s *NodeInventoryHandlerTestSuite) TestStopHandler() {
inventories := make(chan *storage.NodeInventory)
defer close(inventories)
producer := concurrency.NewStopper()
h := NewNodeInventoryHandler(inventories)
h := NewNodeInventoryHandler(inventories, &mockAlwaysHitNodeIDMatcher{})
s.NoError(h.Start())
consumer := consumeAndCount(h.ResponsesC(), 1)
// This is a producer that stops the handler after producing the first message and then sends many (29) more messages.
Expand Down Expand Up @@ -104,7 +104,7 @@ func (s *NodeInventoryHandlerTestSuite) TestStopHandler() {
func (s *NodeInventoryHandlerTestSuite) TestHandlerRegularRoutine() {
ch, producer := s.generateTestInputNoClose(10)
defer close(ch)
h := NewNodeInventoryHandler(ch)
h := NewNodeInventoryHandler(ch, &mockAlwaysHitNodeIDMatcher{})
s.NoError(h.Start())
consumer := consumeAndCount(h.ResponsesC(), 10)
s.NoError(producer.Stopped().Wait())
Expand All @@ -117,7 +117,7 @@ func (s *NodeInventoryHandlerTestSuite) TestHandlerRegularRoutine() {
func (s *NodeInventoryHandlerTestSuite) TestHandlerStopIgnoresError() {
ch, producer := s.generateTestInputNoClose(10)
defer close(ch)
h := NewNodeInventoryHandler(ch)
h := NewNodeInventoryHandler(ch, &mockAlwaysHitNodeIDMatcher{})
s.NoError(h.Start())
consumer := consumeAndCount(h.ResponsesC(), 10)
s.NoError(producer.Stopped().Wait())
Expand Down Expand Up @@ -174,7 +174,7 @@ func consumeAndCount[T any](ch <-chan *T, numToConsume int) concurrency.StopperC
func (s *NodeInventoryHandlerTestSuite) TestMultipleStartHandler() {
ch, producer := s.generateTestInputNoClose(10)
defer close(ch)
h := NewNodeInventoryHandler(ch)
h := NewNodeInventoryHandler(ch, &mockAlwaysHitNodeIDMatcher{})

s.NoError(h.Start())
s.ErrorIs(h.Start(), errStartMoreThanOnce)
Expand All @@ -196,7 +196,7 @@ func (s *NodeInventoryHandlerTestSuite) TestMultipleStartHandler() {
func (s *NodeInventoryHandlerTestSuite) TestDoubleStopHandler() {
ch, producer := s.generateTestInputNoClose(10)
defer close(ch)
h := NewNodeInventoryHandler(ch)
h := NewNodeInventoryHandler(ch, &mockAlwaysHitNodeIDMatcher{})
s.NoError(h.Start())
consumer := consumeAndCount(h.ResponsesC(), 10)
s.NoError(producer.Stopped().Wait())
Expand All @@ -210,7 +210,7 @@ func (s *NodeInventoryHandlerTestSuite) TestDoubleStopHandler() {

func (s *NodeInventoryHandlerTestSuite) TestInputChannelClosed() {
ch, producer := s.generateTestInputNoClose(10)
h := NewNodeInventoryHandler(ch)
h := NewNodeInventoryHandler(ch, &mockAlwaysHitNodeIDMatcher{})
s.NoError(h.Start())
consumer := consumeAndCount(h.ResponsesC(), 10)
s.NoError(producer.Stopped().Wait())
Expand All @@ -221,3 +221,62 @@ func (s *NodeInventoryHandlerTestSuite) TestInputChannelClosed() {
// The handler will stop as there are no more messages to handle
s.ErrorIs(h.Stopped().Wait(), errInputChanClosed)
}

func (s *NodeInventoryHandlerTestSuite) generateNilTestInputNoClose(numToProduce int) (chan *storage.NodeInventory, concurrency.StopperClient) {
input := make(chan *storage.NodeInventory)
st := concurrency.NewStopper()
go func() {
defer st.Flow().ReportStopped()
for i := 0; i < numToProduce; i++ {
select {
case <-st.Flow().StopRequested():
return
case input <- nil:
}
}
}()
return input, st.Client()
}

func (s *NodeInventoryHandlerTestSuite) TestHandlerNilInput() {
ch, producer := s.generateNilTestInputNoClose(10)
defer close(ch)
h := NewNodeInventoryHandler(ch, &mockAlwaysHitNodeIDMatcher{})
s.NoError(h.Start())
consumer := consumeAndCount(h.ResponsesC(), 0)
s.NoError(producer.Stopped().Wait())
s.NoError(consumer.Stopped().Wait())

h.Stop(nil)
s.NoError(h.Stopped().Wait())
}

func (s *NodeInventoryHandlerTestSuite) TestHandlerNodeUnknown() {
ch, producer := s.generateTestInputNoClose(10)
defer close(ch)
h := NewNodeInventoryHandler(ch, &mockNeverHitNodeIDMatcher{})
s.NoError(h.Start())
// expect consumer to get 0 messages - sensor should drop inventory when node is not found
consumer := consumeAndCount(h.ResponsesC(), 0)
s.NoError(producer.Stopped().Wait())
s.NoError(consumer.Stopped().Wait())

h.Stop(nil)
s.NoError(h.Stopped().Wait())
}

// mockAlwaysHitNodeIDMatcher always finds a node when GetNodeResource is called
type mockAlwaysHitNodeIDMatcher struct{}

// GetNodeID always finds a hardcoded ID "abc"
func (c *mockAlwaysHitNodeIDMatcher) GetNodeID(nodename string) (string, error) {
return "abc", nil
}

// mockNeverHitNodeIDMatcher simulates inability to find a node when GetNodeResource is called
type mockNeverHitNodeIDMatcher struct{}

// GetNodeID never finds a node and returns error
func (c *mockNeverHitNodeIDMatcher) GetNodeID(nodename string) (string, error) {
return "", errors.New("cannot find node")
}
32 changes: 32 additions & 0 deletions sensor/common/compliance/node_matcher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package compliance

import (
"fmt"

"github.com/stackrox/rox/sensor/common/store"
)

// NodeIDMatcher helps finding NodeWrap by name
type NodeIDMatcher interface {
GetNodeID(nodename string) (string, error)
}

// NodeIDMatcherImpl finds Node by name within NodeStore
type NodeIDMatcherImpl struct {
nodeStore store.NodeStore
}

// NewNodeIDMatcher creates a NodeIDMatcherImpl
func NewNodeIDMatcher(store store.NodeStore) *NodeIDMatcherImpl {
return &NodeIDMatcherImpl{
nodeStore: store,
}
}

// GetNodeID returns NodeID if a Node with matching name has been found
func (c *NodeIDMatcherImpl) GetNodeID(nodename string) (string, error) {
if node := c.nodeStore.GetNode(nodename); node != nil {
return node.GetId(), nil
}
return "", fmt.Errorf("cannot find node with name '%s'", nodename)
}
81 changes: 81 additions & 0 deletions sensor/common/compliance/node_matcher_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package compliance

import (
"testing"

"github.com/pkg/errors"
"github.com/stackrox/rox/generated/storage"
"github.com/stretchr/testify/suite"
)

func TestNodeIDMatcher(t *testing.T) {
suite.Run(t, &NodeInventoryHandlerImplTestSuite{})
}

var _ suite.TearDownTestSuite = (*NodeInventoryHandlerTestSuite)(nil)

type NodeInventoryHandlerImplTestSuite struct {
suite.Suite
}

func (s *NodeInventoryHandlerImplTestSuite) TestNodeIDMatcherGetNodeID() {
dummy := make(chan *storage.NodeInventory)
defer close(dummy)

tt := map[string]struct {
storageState map[string]string
namesToExpectedIDs map[string]string
}{
"Existing node is found": {
storageState: map[string]string{
"node1": "id1",
},
namesToExpectedIDs: map[string]string{
"node1": "id1",
"node2": "",
},
},
"Empty store": {
storageState: map[string]string{},
namesToExpectedIDs: map[string]string{
"node1": "",
"node2": "",
},
},
}
for name, tc := range tt {
s.Run(name, func() {
matcher := newMockNodeIDMatcher(tc.storageState)
for nodeName, expectedID := range tc.namesToExpectedIDs {
gotID, gotErr := matcher.GetNodeID(nodeName)
s.Equal(expectedID, gotID, "ID mismatch for inventory '%s'", nodeName)
if gotID != "" {
s.Nil(gotErr)
} else {
s.NotNil(gotErr)
}
}
})
}

}

// mockNodeIDMatcher always finds a node when GetNodeResource is called
type mockNodeIDMatcher struct {
nodeStore map[string]string
}

// newMockNodeIDMatcher builds mockNodeIDMatcher
func newMockNodeIDMatcher(store map[string]string) *mockNodeIDMatcher {
return &mockNodeIDMatcher{
nodeStore: store,
}
}

// GetNodeID searches for nodeID in the map and returns it when found
func (c *mockNodeIDMatcher) GetNodeID(nodename string) (string, error) {
if val, ok := c.nodeStore[nodename]; ok {
return val, nil
}
return "", errors.New("node not found")
}
37 changes: 37 additions & 0 deletions sensor/common/store/mocks/types.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions sensor/common/store/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,8 @@ type Provider interface {
type EndpointManager interface {
OnDeploymentCreateOrUpdateByID(id string)
}

// NodeStore represents a collection of Nodes
type NodeStore interface {
GetNode(nodeName string) *storage.Node
}
3 changes: 1 addition & 2 deletions sensor/kubernetes/listener/resources/dispatcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ func NewDispatcherRegistry(
serviceAccountStore := ServiceAccountStoreSingleton()
deploymentStore := storeProvider.deploymentStore
podStore := storeProvider.podStore
nodeStore := storeProvider.nodeStore
nsStore := newNamespaceStore()
netPolicyStore := NetworkPolicySingleton()
endpointManager := storeProvider.endpointManager
Expand All @@ -89,7 +88,7 @@ func NewDispatcherRegistry(
osRouteDispatcher: newRouteDispatcher(serviceStore, portExposureReconciler),
secretDispatcher: newSecretDispatcher(registryStore),
networkPolicyDispatcher: newNetworkPolicyDispatcher(netPolicyStore, deploymentStore),
nodeDispatcher: newNodeDispatcher(deploymentStore, nodeStore, endpointManager),
nodeDispatcher: newNodeDispatcher(deploymentStore, storeProvider.nodeStore, endpointManager),
serviceAccountDispatcher: newServiceAccountDispatcher(serviceAccountStore),
clusterOperatorDispatcher: newClusterOperatorDispatcher(namespaces),

Expand Down
Loading