diff --git a/api/device.go b/api/device.go index d35fe05..7b21dd1 100644 --- a/api/device.go +++ b/api/device.go @@ -59,6 +59,9 @@ type DeviceLocalInterface interface { // Get a FeatureLocalInterface implementation for a given feature address FeatureByAddress(address *model.FeatureAddressType) FeatureLocalInterface + // Clean all entity specific caches + CleanRemoteEntityCaches(remoteAddress *model.EntityAddressType) + // Process incoming SPINE datagram ProcessCmd(datagram model.DatagramType, remoteDevice DeviceRemoteInterface) error diff --git a/api/feature.go b/api/feature.go index f60fe5f..20173bb 100644 --- a/api/feature.go +++ b/api/feature.go @@ -64,8 +64,12 @@ type FeatureLocalInterface interface { // Overwrite the default 1 minute timeout for write approvals SetWriteApprovalTimeout(duration time.Duration) - // Clean all device specific caches - CleanCaches(ski string) + // Clean all write approval caches for a remote device ski + CleanWriteApprovalCaches(ski string) + // Clean all remote device specific caches + CleanRemoteDeviceCaches(remoteAddress *model.DeviceAddressType) + // Clean all remote entity specific caches + CleanRemoteEntityCaches(remoteAddress *model.EntityAddressType) // return all functions Functions() []model.FunctionType diff --git a/mocks/DeviceLocalInterface.go b/mocks/DeviceLocalInterface.go index 55d4bdc..107e169 100644 --- a/mocks/DeviceLocalInterface.go +++ b/mocks/DeviceLocalInterface.go @@ -185,6 +185,39 @@ func (_c *DeviceLocalInterface_BindingManager_Call) RunAndReturn(run func() api. return _c } +// CleanRemoteEntityCaches provides a mock function with given fields: remoteAddress +func (_m *DeviceLocalInterface) CleanRemoteEntityCaches(remoteAddress *model.EntityAddressType) { + _m.Called(remoteAddress) +} + +// DeviceLocalInterface_CleanRemoteEntityCaches_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CleanRemoteEntityCaches' +type DeviceLocalInterface_CleanRemoteEntityCaches_Call struct { + *mock.Call +} + +// CleanRemoteEntityCaches is a helper method to define mock.On call +// - remoteAddress *model.EntityAddressType +func (_e *DeviceLocalInterface_Expecter) CleanRemoteEntityCaches(remoteAddress interface{}) *DeviceLocalInterface_CleanRemoteEntityCaches_Call { + return &DeviceLocalInterface_CleanRemoteEntityCaches_Call{Call: _e.mock.On("CleanRemoteEntityCaches", remoteAddress)} +} + +func (_c *DeviceLocalInterface_CleanRemoteEntityCaches_Call) Run(run func(remoteAddress *model.EntityAddressType)) *DeviceLocalInterface_CleanRemoteEntityCaches_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(*model.EntityAddressType)) + }) + return _c +} + +func (_c *DeviceLocalInterface_CleanRemoteEntityCaches_Call) Return() *DeviceLocalInterface_CleanRemoteEntityCaches_Call { + _c.Call.Return() + return _c +} + +func (_c *DeviceLocalInterface_CleanRemoteEntityCaches_Call) RunAndReturn(run func(*model.EntityAddressType)) *DeviceLocalInterface_CleanRemoteEntityCaches_Call { + _c.Call.Return(run) + return _c +} + // DestinationData provides a mock function with given fields: func (_m *DeviceLocalInterface) DestinationData() model.NodeManagementDestinationDataType { ret := _m.Called() diff --git a/mocks/FeatureLocalInterface.go b/mocks/FeatureLocalInterface.go index fc61d7a..d864da7 100644 --- a/mocks/FeatureLocalInterface.go +++ b/mocks/FeatureLocalInterface.go @@ -326,35 +326,101 @@ func (_c *FeatureLocalInterface_BindToRemote_Call) RunAndReturn(run func(*model. return _c } -// CleanCaches provides a mock function with given fields: ski -func (_m *FeatureLocalInterface) CleanCaches(ski string) { +// CleanRemoteDeviceCaches provides a mock function with given fields: remoteAddress +func (_m *FeatureLocalInterface) CleanRemoteDeviceCaches(remoteAddress *model.DeviceAddressType) { + _m.Called(remoteAddress) +} + +// FeatureLocalInterface_CleanRemoteDeviceCaches_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CleanRemoteDeviceCaches' +type FeatureLocalInterface_CleanRemoteDeviceCaches_Call struct { + *mock.Call +} + +// CleanRemoteDeviceCaches is a helper method to define mock.On call +// - remoteAddress *model.DeviceAddressType +func (_e *FeatureLocalInterface_Expecter) CleanRemoteDeviceCaches(remoteAddress interface{}) *FeatureLocalInterface_CleanRemoteDeviceCaches_Call { + return &FeatureLocalInterface_CleanRemoteDeviceCaches_Call{Call: _e.mock.On("CleanRemoteDeviceCaches", remoteAddress)} +} + +func (_c *FeatureLocalInterface_CleanRemoteDeviceCaches_Call) Run(run func(remoteAddress *model.DeviceAddressType)) *FeatureLocalInterface_CleanRemoteDeviceCaches_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(*model.DeviceAddressType)) + }) + return _c +} + +func (_c *FeatureLocalInterface_CleanRemoteDeviceCaches_Call) Return() *FeatureLocalInterface_CleanRemoteDeviceCaches_Call { + _c.Call.Return() + return _c +} + +func (_c *FeatureLocalInterface_CleanRemoteDeviceCaches_Call) RunAndReturn(run func(*model.DeviceAddressType)) *FeatureLocalInterface_CleanRemoteDeviceCaches_Call { + _c.Call.Return(run) + return _c +} + +// CleanRemoteEntityCaches provides a mock function with given fields: remoteAddress +func (_m *FeatureLocalInterface) CleanRemoteEntityCaches(remoteAddress *model.EntityAddressType) { + _m.Called(remoteAddress) +} + +// FeatureLocalInterface_CleanRemoteEntityCaches_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CleanRemoteEntityCaches' +type FeatureLocalInterface_CleanRemoteEntityCaches_Call struct { + *mock.Call +} + +// CleanRemoteEntityCaches is a helper method to define mock.On call +// - remoteAddress *model.EntityAddressType +func (_e *FeatureLocalInterface_Expecter) CleanRemoteEntityCaches(remoteAddress interface{}) *FeatureLocalInterface_CleanRemoteEntityCaches_Call { + return &FeatureLocalInterface_CleanRemoteEntityCaches_Call{Call: _e.mock.On("CleanRemoteEntityCaches", remoteAddress)} +} + +func (_c *FeatureLocalInterface_CleanRemoteEntityCaches_Call) Run(run func(remoteAddress *model.EntityAddressType)) *FeatureLocalInterface_CleanRemoteEntityCaches_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(*model.EntityAddressType)) + }) + return _c +} + +func (_c *FeatureLocalInterface_CleanRemoteEntityCaches_Call) Return() *FeatureLocalInterface_CleanRemoteEntityCaches_Call { + _c.Call.Return() + return _c +} + +func (_c *FeatureLocalInterface_CleanRemoteEntityCaches_Call) RunAndReturn(run func(*model.EntityAddressType)) *FeatureLocalInterface_CleanRemoteEntityCaches_Call { + _c.Call.Return(run) + return _c +} + +// CleanWriteApprovalCaches provides a mock function with given fields: ski +func (_m *FeatureLocalInterface) CleanWriteApprovalCaches(ski string) { _m.Called(ski) } -// FeatureLocalInterface_CleanCaches_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CleanCaches' -type FeatureLocalInterface_CleanCaches_Call struct { +// FeatureLocalInterface_CleanWriteApprovalCaches_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CleanWriteApprovalCaches' +type FeatureLocalInterface_CleanWriteApprovalCaches_Call struct { *mock.Call } -// CleanCaches is a helper method to define mock.On call +// CleanWriteApprovalCaches is a helper method to define mock.On call // - ski string -func (_e *FeatureLocalInterface_Expecter) CleanCaches(ski interface{}) *FeatureLocalInterface_CleanCaches_Call { - return &FeatureLocalInterface_CleanCaches_Call{Call: _e.mock.On("CleanCaches", ski)} +func (_e *FeatureLocalInterface_Expecter) CleanWriteApprovalCaches(ski interface{}) *FeatureLocalInterface_CleanWriteApprovalCaches_Call { + return &FeatureLocalInterface_CleanWriteApprovalCaches_Call{Call: _e.mock.On("CleanWriteApprovalCaches", ski)} } -func (_c *FeatureLocalInterface_CleanCaches_Call) Run(run func(ski string)) *FeatureLocalInterface_CleanCaches_Call { +func (_c *FeatureLocalInterface_CleanWriteApprovalCaches_Call) Run(run func(ski string)) *FeatureLocalInterface_CleanWriteApprovalCaches_Call { _c.Call.Run(func(args mock.Arguments) { run(args[0].(string)) }) return _c } -func (_c *FeatureLocalInterface_CleanCaches_Call) Return() *FeatureLocalInterface_CleanCaches_Call { +func (_c *FeatureLocalInterface_CleanWriteApprovalCaches_Call) Return() *FeatureLocalInterface_CleanWriteApprovalCaches_Call { _c.Call.Return() return _c } -func (_c *FeatureLocalInterface_CleanCaches_Call) RunAndReturn(run func(string)) *FeatureLocalInterface_CleanCaches_Call { +func (_c *FeatureLocalInterface_CleanWriteApprovalCaches_Call) RunAndReturn(run func(string)) *FeatureLocalInterface_CleanWriteApprovalCaches_Call { _c.Call.Return(run) return _c } diff --git a/mocks/NodeManagementInterface.go b/mocks/NodeManagementInterface.go index 8561965..1705630 100644 --- a/mocks/NodeManagementInterface.go +++ b/mocks/NodeManagementInterface.go @@ -326,35 +326,101 @@ func (_c *NodeManagementInterface_BindToRemote_Call) RunAndReturn(run func(*mode return _c } -// CleanCaches provides a mock function with given fields: ski -func (_m *NodeManagementInterface) CleanCaches(ski string) { +// CleanRemoteDeviceCaches provides a mock function with given fields: remoteAddress +func (_m *NodeManagementInterface) CleanRemoteDeviceCaches(remoteAddress *model.DeviceAddressType) { + _m.Called(remoteAddress) +} + +// NodeManagementInterface_CleanRemoteDeviceCaches_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CleanRemoteDeviceCaches' +type NodeManagementInterface_CleanRemoteDeviceCaches_Call struct { + *mock.Call +} + +// CleanRemoteDeviceCaches is a helper method to define mock.On call +// - remoteAddress *model.DeviceAddressType +func (_e *NodeManagementInterface_Expecter) CleanRemoteDeviceCaches(remoteAddress interface{}) *NodeManagementInterface_CleanRemoteDeviceCaches_Call { + return &NodeManagementInterface_CleanRemoteDeviceCaches_Call{Call: _e.mock.On("CleanRemoteDeviceCaches", remoteAddress)} +} + +func (_c *NodeManagementInterface_CleanRemoteDeviceCaches_Call) Run(run func(remoteAddress *model.DeviceAddressType)) *NodeManagementInterface_CleanRemoteDeviceCaches_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(*model.DeviceAddressType)) + }) + return _c +} + +func (_c *NodeManagementInterface_CleanRemoteDeviceCaches_Call) Return() *NodeManagementInterface_CleanRemoteDeviceCaches_Call { + _c.Call.Return() + return _c +} + +func (_c *NodeManagementInterface_CleanRemoteDeviceCaches_Call) RunAndReturn(run func(*model.DeviceAddressType)) *NodeManagementInterface_CleanRemoteDeviceCaches_Call { + _c.Call.Return(run) + return _c +} + +// CleanRemoteEntityCaches provides a mock function with given fields: remoteAddress +func (_m *NodeManagementInterface) CleanRemoteEntityCaches(remoteAddress *model.EntityAddressType) { + _m.Called(remoteAddress) +} + +// NodeManagementInterface_CleanRemoteEntityCaches_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CleanRemoteEntityCaches' +type NodeManagementInterface_CleanRemoteEntityCaches_Call struct { + *mock.Call +} + +// CleanRemoteEntityCaches is a helper method to define mock.On call +// - remoteAddress *model.EntityAddressType +func (_e *NodeManagementInterface_Expecter) CleanRemoteEntityCaches(remoteAddress interface{}) *NodeManagementInterface_CleanRemoteEntityCaches_Call { + return &NodeManagementInterface_CleanRemoteEntityCaches_Call{Call: _e.mock.On("CleanRemoteEntityCaches", remoteAddress)} +} + +func (_c *NodeManagementInterface_CleanRemoteEntityCaches_Call) Run(run func(remoteAddress *model.EntityAddressType)) *NodeManagementInterface_CleanRemoteEntityCaches_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(*model.EntityAddressType)) + }) + return _c +} + +func (_c *NodeManagementInterface_CleanRemoteEntityCaches_Call) Return() *NodeManagementInterface_CleanRemoteEntityCaches_Call { + _c.Call.Return() + return _c +} + +func (_c *NodeManagementInterface_CleanRemoteEntityCaches_Call) RunAndReturn(run func(*model.EntityAddressType)) *NodeManagementInterface_CleanRemoteEntityCaches_Call { + _c.Call.Return(run) + return _c +} + +// CleanWriteApprovalCaches provides a mock function with given fields: ski +func (_m *NodeManagementInterface) CleanWriteApprovalCaches(ski string) { _m.Called(ski) } -// NodeManagementInterface_CleanCaches_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CleanCaches' -type NodeManagementInterface_CleanCaches_Call struct { +// NodeManagementInterface_CleanWriteApprovalCaches_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CleanWriteApprovalCaches' +type NodeManagementInterface_CleanWriteApprovalCaches_Call struct { *mock.Call } -// CleanCaches is a helper method to define mock.On call +// CleanWriteApprovalCaches is a helper method to define mock.On call // - ski string -func (_e *NodeManagementInterface_Expecter) CleanCaches(ski interface{}) *NodeManagementInterface_CleanCaches_Call { - return &NodeManagementInterface_CleanCaches_Call{Call: _e.mock.On("CleanCaches", ski)} +func (_e *NodeManagementInterface_Expecter) CleanWriteApprovalCaches(ski interface{}) *NodeManagementInterface_CleanWriteApprovalCaches_Call { + return &NodeManagementInterface_CleanWriteApprovalCaches_Call{Call: _e.mock.On("CleanWriteApprovalCaches", ski)} } -func (_c *NodeManagementInterface_CleanCaches_Call) Run(run func(ski string)) *NodeManagementInterface_CleanCaches_Call { +func (_c *NodeManagementInterface_CleanWriteApprovalCaches_Call) Run(run func(ski string)) *NodeManagementInterface_CleanWriteApprovalCaches_Call { _c.Call.Run(func(args mock.Arguments) { run(args[0].(string)) }) return _c } -func (_c *NodeManagementInterface_CleanCaches_Call) Return() *NodeManagementInterface_CleanCaches_Call { +func (_c *NodeManagementInterface_CleanWriteApprovalCaches_Call) Return() *NodeManagementInterface_CleanWriteApprovalCaches_Call { _c.Call.Return() return _c } -func (_c *NodeManagementInterface_CleanCaches_Call) RunAndReturn(run func(string)) *NodeManagementInterface_CleanCaches_Call { +func (_c *NodeManagementInterface_CleanWriteApprovalCaches_Call) RunAndReturn(run func(string)) *NodeManagementInterface_CleanWriteApprovalCaches_Call { _c.Call.Return(run) return _c } diff --git a/spine/device_local.go b/spine/device_local.go index 3e24eb8..2c0d6b6 100644 --- a/spine/device_local.go +++ b/spine/device_local.go @@ -158,7 +158,8 @@ func (r *DeviceLocal) RemoveRemoteDeviceConnection(ski string) { } func (r *DeviceLocal) RemoveRemoteDevice(ski string) { - if r.RemoteDeviceForSki(ski) == nil { + remoteDevice := r.RemoteDeviceForSki(ski) + if remoteDevice == nil { return } @@ -177,10 +178,14 @@ func (r *DeviceLocal) RemoveRemoteDevice(ski string) { _ = Events.unsubscribe(api.EventHandlerLevelCore, r) } + remoteDeviceAddress := &model.DeviceAddressType{ + Device: remoteDevice.Address(), + } // remove all data caches for this device for _, entity := range r.entities { for _, feature := range entity.Features() { - feature.CleanCaches(ski) + feature.CleanWriteApprovalCaches(ski) + feature.CleanRemoteDeviceCaches(remoteDeviceAddress) } } } @@ -287,6 +292,14 @@ func (r *DeviceLocal) FeatureByAddress(address *model.FeatureAddressType) api.Fe return nil } +func (r *DeviceLocal) CleanRemoteEntityCaches(remoteAddress *model.EntityAddressType) { + for _, entity := range r.entities { + for _, feature := range entity.Features() { + feature.CleanRemoteEntityCaches(remoteAddress) + } + } +} + func (r *DeviceLocal) ProcessCmd(datagram model.DatagramType, remoteDevice api.DeviceRemoteInterface) error { destAddr := datagram.Header.AddressDestination localFeature := r.FeatureByAddress(destAddr) diff --git a/spine/feature_local.go b/spine/feature_local.go index d6f7a3a..4e8a6c9 100644 --- a/spine/feature_local.go +++ b/spine/feature_local.go @@ -261,7 +261,7 @@ func (r *FeatureLocal) SetWriteApprovalTimeout(duration time.Duration) { r.writeTimeout = duration } -func (r *FeatureLocal) CleanCaches(ski string) { +func (r *FeatureLocal) CleanWriteApprovalCaches(ski string) { r.muxResponseCB.Lock() defer r.muxResponseCB.Unlock() @@ -269,6 +269,77 @@ func (r *FeatureLocal) CleanCaches(ski string) { delete(r.writeApprovalReceived, ski) } +// Remove subscriptions and bindings from local cache for a remote device +// used if a remote device is getting disconnected +func (r *FeatureLocal) CleanRemoteDeviceCaches(remoteAddress *model.DeviceAddressType) { + if remoteAddress == nil || + remoteAddress.Device == nil { + return + } + + r.mux.Lock() + defer r.mux.Unlock() + + var subscriptions []*model.FeatureAddressType + + for _, item := range r.subscriptions { + if item.Device == nil || + *item.Device != *remoteAddress.Device { + subscriptions = append(subscriptions, item) + } + } + + r.subscriptions = subscriptions + + var bindings []*model.FeatureAddressType + + for _, item := range r.bindings { + if item.Device == nil || + *item.Device != *remoteAddress.Device { + bindings = append(bindings, item) + } + } + + r.bindings = bindings +} + +// Remove subscriptions and bindings from local cache for a remote entity +// used if a remote entity is removed +func (r *FeatureLocal) CleanRemoteEntityCaches(remoteAddress *model.EntityAddressType) { + if remoteAddress == nil || + remoteAddress.Device == nil || + remoteAddress.Entity == nil { + return + } + + r.mux.Lock() + defer r.mux.Unlock() + + var subscriptions []*model.FeatureAddressType + + for _, item := range r.subscriptions { + if item.Device == nil || item.Entity == nil || + *item.Device != *remoteAddress.Device || + !reflect.DeepEqual(item.Entity, remoteAddress.Entity) { + subscriptions = append(subscriptions, item) + } + } + + r.subscriptions = subscriptions + + var bindings []*model.FeatureAddressType + + for _, item := range r.bindings { + if item.Device == nil || item.Entity == nil || + *item.Device != *remoteAddress.Device || + !reflect.DeepEqual(item.Entity, remoteAddress.Entity) { + bindings = append(bindings, item) + } + } + + r.bindings = bindings +} + func (r *FeatureLocal) DataCopy(function model.FunctionType) any { r.mux.Lock() defer r.mux.Unlock() diff --git a/spine/feature_local_test.go b/spine/feature_local_test.go index 74dbb18..8b42d60 100644 --- a/spine/feature_local_test.go +++ b/spine/feature_local_test.go @@ -21,13 +21,14 @@ func TestLocalFeatureTestSuite(t *testing.T) { type LocalFeatureTestSuite struct { suite.Suite - senderMock *mocks.SenderInterface - localDevice *DeviceLocal - localEntity *EntityLocal - function, serverWriteFunction model.FunctionType - featureType, subFeatureType model.FeatureTypeType - msgCounter model.MsgCounterType - remoteFeature, remoteServerFeature, remoteSubFeature api.FeatureRemoteInterface + senderMock *mocks.SenderInterface + localDevice *DeviceLocal + localEntity *EntityLocal + function, serverWriteFunction model.FunctionType + featureType, subFeatureType model.FeatureTypeType + msgCounter model.MsgCounterType + remoteFeature, remote2Feature, + remoteServerFeature, remoteSubFeature api.FeatureRemoteInterface localFeature, localServerFeature, localServerFeatureWrite api.FeatureLocalInterface } @@ -43,9 +44,11 @@ func (s *LocalFeatureTestSuite) BeforeTest(suiteName, testName string) { s.localFeature, s.localServerFeature = createLocalFeatures(s.localEntity, s.featureType, "") _, s.localServerFeatureWrite = createLocalFeatures(s.localEntity, s.subFeatureType, s.serverWriteFunction) - remoteDevice := createRemoteDevice(s.localDevice, s.senderMock) + remoteDevice := createRemoteDevice(s.localDevice, "ski", s.senderMock) + remoteDevice2 := createRemoteDevice(s.localDevice, "iks", s.senderMock) s.remoteFeature, s.remoteServerFeature = createRemoteEntityAndFeature(remoteDevice, 1, s.featureType, s.function) s.remoteSubFeature, _ = createRemoteEntityAndFeature(remoteDevice, 2, s.subFeatureType, s.serverWriteFunction) + s.remote2Feature, _ = createRemoteEntityAndFeature(remoteDevice2, 1, s.featureType, s.function) } func (s *LocalFeatureTestSuite) TestDeviceClassification_Functions() { @@ -249,6 +252,152 @@ func (s *LocalFeatureTestSuite) TestDeviceClassification_Bindings() { s.localFeature.RemoveAllRemoteBindings() } +func (s *LocalFeatureTestSuite) Test_CleanRemoteDeviceCaches() { + s.senderMock.On("Subscribe", mock.Anything, mock.Anything, mock.Anything).Return(&s.msgCounter, nil) + s.senderMock.On("Bind", mock.Anything, mock.Anything, mock.Anything).Return(&s.msgCounter, nil) + + s.localFeature.CleanWriteApprovalCaches("testdummytest") + s.localFeature.CleanRemoteDeviceCaches(nil) + + address := &model.DeviceAddressType{} + s.localFeature.CleanRemoteDeviceCaches(address) + + address.Device = util.Ptr(model.AddressDeviceType("dummy")) + s.localFeature.CleanRemoteDeviceCaches(address) + + address.Device = s.remoteFeature.Address().Device + s.localFeature.CleanRemoteDeviceCaches(address) + + s.localFeature.Device().AddRemoteDeviceForSki(s.remoteFeature.Device().Ski(), s.remoteFeature.Device()) + s.localFeature.Device().AddRemoteDeviceForSki(s.remote2Feature.Device().Ski(), s.remote2Feature.Device()) + + msgCounter, err := s.localFeature.SubscribeToRemote(s.remote2Feature.Address()) + assert.Nil(s.T(), err) + assert.NotNil(s.T(), msgCounter) + + msgCounter, err = s.localFeature.SubscribeToRemote(s.remoteFeature.Address()) + assert.Nil(s.T(), err) + assert.NotNil(s.T(), msgCounter) + + msgCounter, err = s.localFeature.SubscribeToRemote(s.remoteSubFeature.Address()) + assert.Nil(s.T(), err) + assert.NotNil(s.T(), msgCounter) + + value := s.localFeature.HasSubscriptionToRemote(s.remote2Feature.Address()) + assert.True(s.T(), value) + + value = s.localFeature.HasSubscriptionToRemote(s.remoteFeature.Address()) + assert.True(s.T(), value) + + value = s.localFeature.HasSubscriptionToRemote(s.remoteSubFeature.Address()) + assert.True(s.T(), value) + + msgCounter, err = s.localFeature.BindToRemote(s.remote2Feature.Address()) + assert.Nil(s.T(), err) + assert.NotNil(s.T(), msgCounter) + + msgCounter, err = s.localFeature.BindToRemote(s.remoteFeature.Address()) + assert.Nil(s.T(), err) + assert.NotNil(s.T(), msgCounter) + + msgCounter, err = s.localFeature.BindToRemote(s.remoteSubFeature.Address()) + assert.Nil(s.T(), err) + assert.NotNil(s.T(), msgCounter) + + value = s.localFeature.HasBindingToRemote(s.remoteFeature.Address()) + assert.True(s.T(), value) + + value = s.localFeature.HasBindingToRemote(s.remoteSubFeature.Address()) + assert.True(s.T(), value) + + s.localFeature.CleanRemoteDeviceCaches(address) + + value = s.localFeature.HasSubscriptionToRemote(s.remote2Feature.Address()) + assert.True(s.T(), value) + + value = s.localFeature.HasSubscriptionToRemote(s.remoteFeature.Address()) + assert.False(s.T(), value) + + value = s.localFeature.HasSubscriptionToRemote(s.remoteSubFeature.Address()) + assert.False(s.T(), value) + + value = s.localFeature.HasBindingToRemote(s.remote2Feature.Address()) + assert.True(s.T(), value) + + value = s.localFeature.HasBindingToRemote(s.remoteFeature.Address()) + assert.False(s.T(), value) + + value = s.localFeature.HasBindingToRemote(s.remoteSubFeature.Address()) + assert.False(s.T(), value) +} + +func (s *LocalFeatureTestSuite) Test_CleanRemoteEntityCaches() { + s.senderMock.On("Subscribe", mock.Anything, mock.Anything, mock.Anything).Return(&s.msgCounter, nil) + s.senderMock.On("Bind", mock.Anything, mock.Anything, mock.Anything).Return(&s.msgCounter, nil) + + s.localFeature.CleanWriteApprovalCaches("testdummytest") + s.localFeature.CleanRemoteEntityCaches(nil) + + address := &model.EntityAddressType{} + s.localFeature.CleanRemoteEntityCaches(address) + + address.Device = util.Ptr(model.AddressDeviceType("dummy")) + s.localFeature.CleanRemoteEntityCaches(address) + + address.Entity = []model.AddressEntityType{10} + s.localFeature.CleanRemoteEntityCaches(address) + + address.Device = s.remoteFeature.Address().Device + s.localFeature.CleanRemoteEntityCaches(address) + + address.Entity = s.remoteFeature.Address().Entity + s.localFeature.CleanRemoteEntityCaches(address) + + s.localFeature.Device().AddRemoteDeviceForSki(s.remoteFeature.Device().Ski(), s.remoteFeature.Device()) + + msgCounter, err := s.localFeature.SubscribeToRemote(s.remoteFeature.Address()) + assert.Nil(s.T(), err) + assert.NotNil(s.T(), msgCounter) + + msgCounter, err = s.localFeature.SubscribeToRemote(s.remoteSubFeature.Address()) + assert.Nil(s.T(), err) + assert.NotNil(s.T(), msgCounter) + + binding := s.localFeature.HasSubscriptionToRemote(s.remoteFeature.Address()) + assert.True(s.T(), binding) + + binding = s.localFeature.HasSubscriptionToRemote(s.remoteSubFeature.Address()) + assert.True(s.T(), binding) + + msgCounter, err = s.localFeature.BindToRemote(s.remoteFeature.Address()) + assert.Nil(s.T(), err) + assert.NotNil(s.T(), msgCounter) + + msgCounter, err = s.localFeature.BindToRemote(s.remoteSubFeature.Address()) + assert.Nil(s.T(), err) + assert.NotNil(s.T(), msgCounter) + + binding = s.localFeature.HasBindingToRemote(s.remoteFeature.Address()) + assert.True(s.T(), binding) + + binding = s.localFeature.HasBindingToRemote(s.remoteSubFeature.Address()) + assert.True(s.T(), binding) + + s.localFeature.CleanRemoteEntityCaches(address) + + binding = s.localFeature.HasSubscriptionToRemote(s.remoteFeature.Address()) + assert.False(s.T(), binding) + + binding = s.localFeature.HasSubscriptionToRemote(s.remoteSubFeature.Address()) + assert.True(s.T(), binding) + + binding = s.localFeature.HasBindingToRemote(s.remoteFeature.Address()) + assert.False(s.T(), binding) + + binding = s.localFeature.HasBindingToRemote(s.remoteSubFeature.Address()) + assert.True(s.T(), binding) +} + func (s *LocalFeatureTestSuite) Test_HandleMessage() { msg := &api.Message{ FeatureRemote: s.remoteServerFeature, diff --git a/spine/helper_test.go b/spine/helper_test.go index 8f38795..75f2829 100644 --- a/spine/helper_test.go +++ b/spine/helper_test.go @@ -194,9 +194,9 @@ func createLocalFeatures(localEntity *EntityLocal, featureType model.FeatureType return localFeature, localServerFeature } -func createRemoteDevice(localDevice *DeviceLocal, sender api.SenderInterface) *DeviceRemote { - remoteDevice := NewDeviceRemote(localDevice, "ski", sender) - remoteDevice.address = util.Ptr(model.AddressDeviceType("Address")) +func createRemoteDevice(localDevice *DeviceLocal, ski string, sender api.SenderInterface) *DeviceRemote { + remoteDevice := NewDeviceRemote(localDevice, ski, sender) + remoteDevice.address = util.Ptr(model.AddressDeviceType(fmt.Sprintf("Address%s", ski))) return remoteDevice } diff --git a/spine/nodemanagement_detaileddiscovery.go b/spine/nodemanagement_detaileddiscovery.go index 7a3d9bd..ee6363c 100644 --- a/spine/nodemanagement_detaileddiscovery.go +++ b/spine/nodemanagement_detaileddiscovery.go @@ -291,6 +291,9 @@ func (r *NodeManagement) processNotifyDetailedDiscoveryData(message *api.Message // remove all bindings for this entity bindingMgr := r.Device().BindingManager() bindingMgr.RemoveBindingsForEntity(removedEntity) + + // remove all feature caches for this entity + r.Device().CleanRemoteEntityCaches(removedEntity.Address()) } } } diff --git a/spine/nodemanagement_test.go b/spine/nodemanagement_test.go index 93e41f2..c21fcc4 100644 --- a/spine/nodemanagement_test.go +++ b/spine/nodemanagement_test.go @@ -20,7 +20,7 @@ func TestNodemanagement_BindingCalls(t *testing.T) { localDevice, localEntity := createLocalDeviceAndEntity(bindingEntityId) _, serverFeature := createLocalFeatures(localEntity, featureType, "") - remoteDevice := createRemoteDevice(localDevice, senderMock) + remoteDevice := createRemoteDevice(localDevice, "ski", senderMock) clientFeature, _ := createRemoteEntityAndFeature(remoteDevice, bindingEntityId, featureType, "") senderMock.On("Reply", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { @@ -93,7 +93,7 @@ func TestNodemanagement_SubscriptionCalls(t *testing.T) { localDevice, localEntity := createLocalDeviceAndEntity(subscriptionEntityId) _, serverFeature := createLocalFeatures(localEntity, featureType, "") - remoteDevice := createRemoteDevice(localDevice, senderMock) + remoteDevice := createRemoteDevice(localDevice, "ski", senderMock) clientFeature, _ := createRemoteEntityAndFeature(remoteDevice, subscriptionEntityId, featureType, "") senderMock.On("Reply", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) {