From 7ec95ab929db9460092abdce458a262c7cf89068 Mon Sep 17 00:00:00 2001 From: Ginny Guan Date: Wed, 8 Feb 2023 15:56:45 +0800 Subject: [PATCH] feat: Implement Device Profile System Events - publish system events for device profile add/update/delete - remove the update device profile REST callback close #4273 Signed-off-by: Ginny Guan --- go.mod | 2 +- go.sum | 4 +- internal/core/metadata/application/device.go | 55 ++------- .../metadata/application/devicecommand.go | 7 +- .../metadata/application/deviceprofile.go | 30 ++++- .../metadata/application/deviceresource.go | 7 +- internal/core/metadata/application/notify.go | 111 +++++++++++------- .../{device_test.go => notify_test.go} | 104 ++++++++++------ .../metadata/controller/http/devicecommand.go | 2 +- .../controller/http/deviceprofile_test.go | 3 + .../controller/http/deviceresource.go | 2 +- 11 files changed, 195 insertions(+), 132 deletions(-) rename internal/core/metadata/application/{device_test.go => notify_test.go} (54%) diff --git a/go.mod b/go.mod index 48812e5e2b..47f105a44a 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ require ( github.com/eclipse/paho.mqtt.golang v1.4.2 github.com/edgexfoundry/go-mod-bootstrap/v3 v3.0.0-dev.17 github.com/edgexfoundry/go-mod-configuration/v3 v3.0.0-dev.2 - github.com/edgexfoundry/go-mod-core-contracts/v3 v3.0.0-dev.9 + github.com/edgexfoundry/go-mod-core-contracts/v3 v3.0.0-dev.10 github.com/edgexfoundry/go-mod-messaging/v3 v3.0.0-dev.7 github.com/edgexfoundry/go-mod-secrets/v3 v3.0.0-dev.5 github.com/fxamacker/cbor/v2 v2.4.0 diff --git a/go.sum b/go.sum index d63d0679df..510ffa6c6f 100644 --- a/go.sum +++ b/go.sum @@ -30,8 +30,8 @@ github.com/edgexfoundry/go-mod-bootstrap/v3 v3.0.0-dev.17 h1:WAbuyikabO+RGh+4HMm github.com/edgexfoundry/go-mod-bootstrap/v3 v3.0.0-dev.17/go.mod h1:saCEXtGUBKvNfMHrnrZyqZYMzdrETOjo5mq0TxCtCRY= github.com/edgexfoundry/go-mod-configuration/v3 v3.0.0-dev.2 h1:xp5MsP+qf/fuJxy8fT7k1N+c4j4C6w04qMCBXm6id7o= github.com/edgexfoundry/go-mod-configuration/v3 v3.0.0-dev.2/go.mod h1:1Vv4uWAo6r7k6jUlqVJW8JOL6YKVBc6sRL8Al3DrMck= -github.com/edgexfoundry/go-mod-core-contracts/v3 v3.0.0-dev.9 h1:ejafyDHaVCdfKW4IQZeg4n1mBI2JkC1Y1XXelyyj+z8= -github.com/edgexfoundry/go-mod-core-contracts/v3 v3.0.0-dev.9/go.mod h1:4lpZUM54ZareGU/yuAJvLEw0BoJ43SvCj1LO+gsKm9c= +github.com/edgexfoundry/go-mod-core-contracts/v3 v3.0.0-dev.10 h1:o5yenvmLn8+0AOz0d5GIek011Tt5ZRbvPlgE4VhozEU= +github.com/edgexfoundry/go-mod-core-contracts/v3 v3.0.0-dev.10/go.mod h1:4lpZUM54ZareGU/yuAJvLEw0BoJ43SvCj1LO+gsKm9c= github.com/edgexfoundry/go-mod-messaging/v3 v3.0.0-dev.7 h1:jgDQA/7SENURXQkIX11pNgA/pX9IK9ZULenj/vF17Vw= github.com/edgexfoundry/go-mod-messaging/v3 v3.0.0-dev.7/go.mod h1:r6Klfz+QBDx1Z5UV0z70MKdK2/cgHwhtqTm2HFXoWug= github.com/edgexfoundry/go-mod-registry/v3 v3.0.0-dev.3 h1:QgZF9f70Cwpvkjw3tP1aiVGHc+yNFJNzW6hO8pDs3fg= diff --git a/internal/core/metadata/application/device.go b/internal/core/metadata/application/device.go index 22be40d27d..9294d7ecfd 100644 --- a/internal/core/metadata/application/device.go +++ b/internal/core/metadata/application/device.go @@ -8,20 +8,16 @@ package application import ( "context" - "encoding/json" goErrors "errors" "fmt" bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/container" - config2 "github.com/edgexfoundry/go-mod-bootstrap/v3/config" "github.com/edgexfoundry/go-mod-bootstrap/v3/di" - "github.com/edgexfoundry/go-mod-core-contracts/v3/clients/logger" "github.com/edgexfoundry/go-mod-core-contracts/v3/common" "github.com/edgexfoundry/go-mod-core-contracts/v3/dtos" "github.com/edgexfoundry/go-mod-core-contracts/v3/dtos/requests" "github.com/edgexfoundry/go-mod-core-contracts/v3/errors" "github.com/edgexfoundry/go-mod-core-contracts/v3/models" - "github.com/edgexfoundry/go-mod-messaging/v3/pkg/types" "github.com/edgexfoundry/edgex-go/internal/core/metadata/container" "github.com/edgexfoundry/edgex-go/internal/core/metadata/infrastructure/interfaces" @@ -50,15 +46,14 @@ func AddDevice(d models.Device, ctx context.Context, dic *di.Container) (id stri correlation.FromContext(ctx), ) - go publishDeviceSystemEvent(common.DeviceSystemEventActionAdd, d.ServiceName, d, ctx, lc, dic) + deviceDTO := dtos.FromDeviceModelToDTO(addedDevice) + go publishSystemEvent(common.DeviceSystemEventType, common.SystemEventActionAdd, d.ServiceName, deviceDTO, ctx, dic) return addedDevice.Id, nil } // DeleteDeviceByName deletes the device by name func DeleteDeviceByName(name string, ctx context.Context, dic *di.Container) errors.EdgeX { - lc := bootstrapContainer.LoggingClientFrom(dic.Get) - if name == "" { return errors.NewCommonEdgeX(errors.KindContractInvalid, "name is empty", nil) } @@ -72,7 +67,8 @@ func DeleteDeviceByName(name string, ctx context.Context, dic *di.Container) err return errors.NewCommonEdgeXWrapper(err) } - go publishDeviceSystemEvent(common.DeviceSystemEventActionDelete, device.ServiceName, device, ctx, lc, dic) + deviceDTO := dtos.FromDeviceModelToDTO(device) + go publishSystemEvent(common.DeviceSystemEventType, common.SystemEventActionDelete, device.ServiceName, deviceDTO, ctx, dic) return nil } @@ -128,7 +124,8 @@ func PatchDevice(dto dtos.UpdateDevice, ctx context.Context, dic *di.Container) requests.ReplaceDeviceModelFieldsWithDTO(&device, dto) - err = validateDeviceCallback(ctx, dic, dtos.FromDeviceModelToDTO(device)) + deviceDTO := dtos.FromDeviceModelToDTO(device) + err = validateDeviceCallback(ctx, dic, deviceDTO) if err != nil { return errors.NewCommonEdgeXWrapper(err) } @@ -144,10 +141,10 @@ func PatchDevice(dto dtos.UpdateDevice, ctx context.Context, dic *di.Container) ) if oldServiceName != "" { - go publishDeviceSystemEvent(common.DeviceSystemEventActionUpdate, oldServiceName, device, ctx, lc, dic) + go publishSystemEvent(common.DeviceSystemEventType, common.SystemEventActionUpdate, oldServiceName, deviceDTO, ctx, dic) } - go publishDeviceSystemEvent(common.DeviceSystemEventActionUpdate, device.ServiceName, device, ctx, lc, dic) + go publishSystemEvent(common.DeviceSystemEventType, common.SystemEventActionUpdate, device.ServiceName, deviceDTO, ctx, dic) return nil } @@ -223,39 +220,3 @@ func DevicesByProfileName(offset int, limit int, profileName string, dic *di.Con } var noMessagingClientError = goErrors.New("MessageBus Client not available. Please update RequireMessageBus and MessageBus configuration to enable sending System Events via the EdgeX MessageBus") - -func publishDeviceSystemEvent(action string, owner string, d models.Device, ctx context.Context, lc logger.LoggingClient, dic *di.Container) { - device := dtos.FromDeviceModelToDTO(d) - systemEvent := dtos.NewSystemEvent(common.DeviceSystemEventType, action, common.CoreMetaDataServiceKey, owner, nil, device) - - messagingClient := bootstrapContainer.MessagingClientFrom(dic.Get) - if messagingClient == nil { - lc.Errorf("unable to publish Device System Event: %v", noMessagingClientError) - return - } - - config := container.ConfigurationFrom(dic.Get) - - prefix := config.MessageBus.Topics[config2.MessageBusPublishTopicPrefix] - publishTopic := fmt.Sprintf("%s/%s/%s/%s/%s/%s", - prefix, - systemEvent.Source, - systemEvent.Type, - systemEvent.Action, - systemEvent.Owner, - device.ProfileName) - - payload, _ := json.Marshal(systemEvent) - envelope := types.NewMessageEnvelope(payload, ctx) - // Correlation ID and Content type are set by the above factory function from the context of the request that - // triggered this System Event. We'll keep that Correlation ID, but need to make sure the Content Type is set appropriate - // for how the payload was encoded above. - envelope.ContentType = common.ContentTypeJSON - - if err := messagingClient.Publish(envelope, publishTopic); err != nil { - lc.Errorf("unable to publish '%s' Device System Event for device '%s' to topic '%s': %v", action, device.Name, publishTopic, err) - return - } - - lc.Debugf("Published the '%s' Device System Event for device '%s' to topic '%s'", action, device.Name, publishTopic) -} diff --git a/internal/core/metadata/application/devicecommand.go b/internal/core/metadata/application/devicecommand.go index dc5425f7f1..ec2e10478f 100644 --- a/internal/core/metadata/application/devicecommand.go +++ b/internal/core/metadata/application/devicecommand.go @@ -7,6 +7,7 @@ package application import ( "context" + "github.com/edgexfoundry/go-mod-core-contracts/v3/common" "github.com/edgexfoundry/edgex-go/internal/core/metadata/container" "github.com/edgexfoundry/edgex-go/internal/pkg/correlation" @@ -43,6 +44,7 @@ func AddDeviceProfileDeviceCommand(profileName string, deviceCommand models.Devi } lc.Debugf("DeviceProfile deviceCommands added on DB successfully. Correlation-id: %s ", correlation.FromContext(ctx)) + go publishSystemEvent(common.DeviceProfileSystemEventType, common.SystemEventActionUpdate, common.CoreMetaDataServiceKey, profileDTO, ctx, dic) return nil } @@ -76,11 +78,13 @@ func PatchDeviceProfileDeviceCommand(profileName string, dto dtos.UpdateDeviceCo } lc.Debugf("DeviceProfile deviceCommands patched on DB successfully. Correlation-id: %s ", correlation.FromContext(ctx)) + profileDTO := dtos.FromDeviceProfileModelToDTO(profile) + go publishSystemEvent(common.DeviceProfileSystemEventType, common.SystemEventActionUpdate, common.CoreMetaDataServiceKey, profileDTO, ctx, dic) return nil } -func DeleteDeviceCommandByName(profileName string, commandName string, dic *di.Container) errors.EdgeX { +func DeleteDeviceCommandByName(profileName string, commandName string, ctx context.Context, dic *di.Container) errors.EdgeX { if profileName == "" { return errors.NewCommonEdgeX(errors.KindContractInvalid, "profile name is empty", nil) } @@ -130,5 +134,6 @@ func DeleteDeviceCommandByName(profileName string, commandName string, dic *di.C return errors.NewCommonEdgeXWrapper(err) } + go publishSystemEvent(common.DeviceProfileSystemEventType, common.SystemEventActionUpdate, common.CoreMetaDataServiceKey, profileDTO, ctx, dic) return nil } diff --git a/internal/core/metadata/application/deviceprofile.go b/internal/core/metadata/application/deviceprofile.go index 519fb416b1..9b8c009eb8 100644 --- a/internal/core/metadata/application/deviceprofile.go +++ b/internal/core/metadata/application/deviceprofile.go @@ -9,8 +9,6 @@ import ( "context" "fmt" - "github.com/edgexfoundry/go-mod-core-contracts/v3/dtos/requests" - "github.com/edgexfoundry/edgex-go/internal/core/metadata/container" "github.com/edgexfoundry/edgex-go/internal/core/metadata/infrastructure/interfaces" "github.com/edgexfoundry/edgex-go/internal/pkg/correlation" @@ -18,7 +16,9 @@ import ( bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/container" "github.com/edgexfoundry/go-mod-bootstrap/v3/di" + "github.com/edgexfoundry/go-mod-core-contracts/v3/common" "github.com/edgexfoundry/go-mod-core-contracts/v3/dtos" + "github.com/edgexfoundry/go-mod-core-contracts/v3/dtos/requests" "github.com/edgexfoundry/go-mod-core-contracts/v3/errors" "github.com/edgexfoundry/go-mod-core-contracts/v3/models" ) @@ -46,6 +46,9 @@ func AddDeviceProfile(d models.DeviceProfile, ctx context.Context, dic *di.Conta correlationId, ) + profileDTO := dtos.FromDeviceProfileModelToDTO(addedDeviceProfile) + go publishSystemEvent(common.DeviceProfileSystemEventType, common.SystemEventActionAdd, common.CoreMetaDataServiceKey, profileDTO, ctx, dic) + return addedDeviceProfile.Id, nil } @@ -69,7 +72,15 @@ func UpdateDeviceProfile(d models.DeviceProfile, ctx context.Context, dic *di.Co "DeviceProfile updated on DB successfully. Correlation-id: %s ", correlation.FromContext(ctx), ) - go updateDeviceProfileCallback(ctx, dic, dtos.FromDeviceProfileModelToDTO(d)) + + profile, err := dbClient.DeviceProfileByName(d.Name) + if err != nil { + return errors.NewCommonEdgeXWrapper(err) + } + + profileDTO := dtos.FromDeviceProfileModelToDTO(profile) + go publishSystemEvent(common.DeviceProfileSystemEventType, common.SystemEventActionUpdate, common.CoreMetaDataServiceKey, profileDTO, ctx, dic) + return nil } @@ -97,10 +108,18 @@ func DeleteDeviceProfileByName(name string, ctx context.Context, dic *di.Contain return errors.NewCommonEdgeX(errors.KindContractInvalid, "name is empty", nil) } dbClient := container.DBClientFrom(dic.Get) - err := dbClient.DeleteDeviceProfileByName(name) + profile, err := dbClient.DeviceProfileByName(name) + if err != nil { + return errors.NewCommonEdgeXWrapper(err) + } + err = dbClient.DeleteDeviceProfileByName(name) if err != nil { return errors.NewCommonEdgeXWrapper(err) } + + profileDTO := dtos.FromDeviceProfileModelToDTO(profile) + go publishSystemEvent(common.DeviceProfileSystemEventType, common.SystemEventActionDelete, common.CoreMetaDataServiceKey, profileDTO, ctx, dic) + return nil } @@ -201,6 +220,9 @@ func PatchDeviceProfileBasicInfo(ctx context.Context, dto dtos.UpdateDeviceProfi correlation.FromContext(ctx), ) + profileDTO := dtos.FromDeviceProfileModelToDTO(deviceProfile) + go publishSystemEvent(common.DeviceProfileSystemEventType, common.SystemEventActionUpdate, common.CoreMetaDataServiceKey, profileDTO, ctx, dic) + return nil } diff --git a/internal/core/metadata/application/deviceresource.go b/internal/core/metadata/application/deviceresource.go index 745c983304..7b192fd047 100644 --- a/internal/core/metadata/application/deviceresource.go +++ b/internal/core/metadata/application/deviceresource.go @@ -14,6 +14,7 @@ import ( bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/container" "github.com/edgexfoundry/go-mod-bootstrap/v3/di" + "github.com/edgexfoundry/go-mod-core-contracts/v3/common" "github.com/edgexfoundry/go-mod-core-contracts/v3/dtos" "github.com/edgexfoundry/go-mod-core-contracts/v3/dtos/requests" "github.com/edgexfoundry/go-mod-core-contracts/v3/errors" @@ -79,6 +80,7 @@ func AddDeviceProfileResource(profileName string, resource models.DeviceResource } lc.Debugf("DeviceProfile deviceResources added on DB successfully. Correlation-id: %s ", correlation.FromContext(ctx)) + go publishSystemEvent(common.DeviceProfileSystemEventType, common.SystemEventActionUpdate, common.CoreMetaDataServiceKey, profileDTO, ctx, dic) return nil } @@ -112,11 +114,13 @@ func PatchDeviceProfileResource(profileName string, dto dtos.UpdateDeviceResourc } lc.Debugf("DeviceProfile deviceResources patched on DB successfully. Correlation-id: %s ", correlation.FromContext(ctx)) + profileDTO := dtos.FromDeviceProfileModelToDTO(profile) + go publishSystemEvent(common.DeviceProfileSystemEventType, common.SystemEventActionUpdate, common.CoreMetaDataServiceKey, profileDTO, ctx, dic) return nil } -func DeleteDeviceResourceByName(profileName string, resourceName string, dic *di.Container) errors.EdgeX { +func DeleteDeviceResourceByName(profileName string, resourceName string, ctx context.Context, dic *di.Container) errors.EdgeX { if profileName == "" { return errors.NewCommonEdgeX(errors.KindContractInvalid, "profile name is empty", nil) } @@ -166,6 +170,7 @@ func DeleteDeviceResourceByName(profileName string, resourceName string, dic *di return errors.NewCommonEdgeXWrapper(err) } + go publishSystemEvent(common.DeviceProfileSystemEventType, common.SystemEventActionUpdate, common.CoreMetaDataServiceKey, profileDTO, ctx, dic) return nil } diff --git a/internal/core/metadata/application/notify.go b/internal/core/metadata/application/notify.go index 27119dcad9..a622a4435d 100644 --- a/internal/core/metadata/application/notify.go +++ b/internal/core/metadata/application/notify.go @@ -7,16 +7,23 @@ package application import ( "context" + "encoding/json" + "fmt" "net/http" - "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/container" + bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/container" + config2 "github.com/edgexfoundry/go-mod-bootstrap/v3/config" "github.com/edgexfoundry/go-mod-bootstrap/v3/di" clients "github.com/edgexfoundry/go-mod-core-contracts/v3/clients/http" "github.com/edgexfoundry/go-mod-core-contracts/v3/clients/interfaces" + "github.com/edgexfoundry/go-mod-core-contracts/v3/common" "github.com/edgexfoundry/go-mod-core-contracts/v3/dtos" "github.com/edgexfoundry/go-mod-core-contracts/v3/dtos/requests" "github.com/edgexfoundry/go-mod-core-contracts/v3/errors" "github.com/edgexfoundry/go-mod-core-contracts/v3/models" + "github.com/edgexfoundry/go-mod-messaging/v3/pkg/types" + + "github.com/edgexfoundry/edgex-go/internal/core/metadata/container" ) func newDeviceServiceCallbackClient(ctx context.Context, dic *di.Container, deviceServiceName string) (interfaces.DeviceServiceCallbackClient, errors.EdgeX) { @@ -29,7 +36,7 @@ func newDeviceServiceCallbackClient(ctx context.Context, dic *di.Container, devi // validateDeviceCallback invoke device service's validation function for validating new or updated device func validateDeviceCallback(ctx context.Context, dic *di.Container, device dtos.Device) errors.EdgeX { - lc := container.LoggingClientFrom(dic.Get) + lc := bootstrapContainer.LoggingClientFrom(dic.Get) deviceServiceCallbackClient, err := newDeviceServiceCallbackClient(ctx, dic, device.ServiceName) if err != nil { lc.Errorf("fail to create a device service callback client by serviceName %s, err: %v", device.ServiceName, err) @@ -55,44 +62,9 @@ func validateDeviceCallback(ctx context.Context, dic *di.Container, device dtos. return nil } -// updateDeviceProfileCallback invoke device service's callback function for updating device profile -func updateDeviceProfileCallback(ctx context.Context, dic *di.Container, deviceProfile dtos.DeviceProfile) { - lc := container.LoggingClientFrom(dic.Get) - devices, _, err := DevicesByProfileName(0, -1, deviceProfile.Name, dic) - if err != nil { - lc.Errorf("fail to query associated devices by deviceProfile name %s, err: %v", deviceProfile.Name, err) - return - } - // Invoke callback for each device service - dsMap := make(map[string]bool) - for _, d := range devices { - if _, ok := dsMap[d.ServiceName]; ok { - // skip the invoked device service - continue - } - dsMap[d.ServiceName] = true - - deviceServiceCallbackClient, err := newDeviceServiceCallbackClient(ctx, dic, d.ServiceName) - if err != nil { - lc.Errorf("fail to new a device service callback client by serviceName %s, err: %v", d.ServiceName, err) - continue - } - - request := requests.NewDeviceProfileRequest(deviceProfile) - response, err := deviceServiceCallbackClient.UpdateDeviceProfileCallback(ctx, request) - if err != nil { - lc.Errorf("fail to invoke device service callback for updating device profile %s, err: %v", deviceProfile.Name, err) - continue - } - if response.StatusCode != http.StatusOK { - lc.Errorf("fail to invoke device service callback for updating device profile %s, err: %s", deviceProfile.Name, response.Message) - } - } -} - // addProvisionWatcherCallback invoke device service's callback function for adding new provision watcher func addProvisionWatcherCallback(ctx context.Context, dic *di.Container, pw dtos.ProvisionWatcher) { - lc := container.LoggingClientFrom(dic.Get) + lc := bootstrapContainer.LoggingClientFrom(dic.Get) deviceServiceCallbackClient, err := newDeviceServiceCallbackClient(ctx, dic, pw.ServiceName) if err != nil { lc.Errorf("fail to new a device service callback client by serviceName %s, err: %v", pw.ServiceName, err) @@ -112,7 +84,7 @@ func addProvisionWatcherCallback(ctx context.Context, dic *di.Container, pw dtos // updateProvisionWatcherCallback invoke device service's callback function for updating provision watcher func updateProvisionWatcherCallback(ctx context.Context, dic *di.Container, serviceName string, pw models.ProvisionWatcher) { - lc := container.LoggingClientFrom(dic.Get) + lc := bootstrapContainer.LoggingClientFrom(dic.Get) deviceServiceCallbackClient, err := newDeviceServiceCallbackClient(ctx, dic, serviceName) if err != nil { lc.Errorf("fail to new a device service callback client by serviceName %s, err: %v", serviceName, err) @@ -132,7 +104,7 @@ func updateProvisionWatcherCallback(ctx context.Context, dic *di.Container, serv // deleteProvisionWatcherCallback invoke device service's callback function for deleting provision watcher func deleteProvisionWatcherCallback(ctx context.Context, dic *di.Container, pw models.ProvisionWatcher) { - lc := container.LoggingClientFrom(dic.Get) + lc := bootstrapContainer.LoggingClientFrom(dic.Get) deviceServiceCallbackClient, err := newDeviceServiceCallbackClient(ctx, dic, pw.ServiceName) if err != nil { lc.Errorf("fail to new a device service callback client by serviceName %s, err: %v", pw.ServiceName, err) @@ -150,7 +122,7 @@ func deleteProvisionWatcherCallback(ctx context.Context, dic *di.Container, pw m // updateDeviceServiceCallback invoke device service's callback function for updating device service func updateDeviceServiceCallback(ctx context.Context, dic *di.Container, ds models.DeviceService) { - lc := container.LoggingClientFrom(dic.Get) + lc := bootstrapContainer.LoggingClientFrom(dic.Get) deviceServiceCallbackClient, err := newDeviceServiceCallbackClient(ctx, dic, ds.Name) if err != nil { lc.Errorf("fail to new a device service callback client by serviceName %s, err: %v", ds.Name, err) @@ -167,3 +139,60 @@ func updateDeviceServiceCallback(ctx context.Context, dic *di.Container, ds mode lc.Errorf("fail to invoke device service callback for updating device service %s, err: %s", ds.Name, response.Message) } } + +func publishSystemEvent(eventType, action, owner string, dto any, ctx context.Context, dic *di.Container) { + lc := bootstrapContainer.LoggingClientFrom(dic.Get) + systemEvent := dtos.NewSystemEvent(eventType, action, common.CoreMetaDataServiceKey, owner, nil, dto) + messagingClient := bootstrapContainer.MessagingClientFrom(dic.Get) + if messagingClient == nil { + lc.Errorf("unable to publish '%s' System Event: %v", eventType, noMessagingClientError) + return + } + + var profileName, detailName string + switch eventType { + case common.DeviceSystemEventType: + if device, ok := dto.(dtos.Device); ok { + profileName = device.ProfileName + detailName = device.Name + } else { + lc.Errorf("can not convert to device DTO") + return + } + case common.DeviceProfileSystemEventType: + if profile, ok := dto.(dtos.DeviceProfile); ok { + profileName = profile.Name + detailName = profile.Name + } else { + lc.Errorf("can not convert to device profile DTO") + return + } + default: + lc.Errorf("unrecognized system event details") + return + } + + config := container.ConfigurationFrom(dic.Get) + prefix := config.MessageBus.Topics[config2.MessageBusPublishTopicPrefix] + publishTopic := fmt.Sprintf("%s/%s/%s/%s/%s/%s", + prefix, + systemEvent.Source, + systemEvent.Type, + systemEvent.Action, + systemEvent.Owner, + profileName) + + payload, _ := json.Marshal(systemEvent) + envelope := types.NewMessageEnvelope(payload, ctx) + // Correlation ID and Content type are set by the above factory function from the context of the request that + // triggered this System Event. We'll keep that Correlation ID, but need to make sure the Content Type is set appropriate + // for how the payload was encoded above. + envelope.ContentType = common.ContentTypeJSON + + if err := messagingClient.Publish(envelope, publishTopic); err != nil { + lc.Errorf("unable to publish '%s' System Event for %s '%s' to topic '%s': %v", action, eventType, detailName, publishTopic, err) + return + } + + lc.Debugf("Published the '%s' System Event for %s '%s' to topic '%s'", action, eventType, detailName, publishTopic) +} diff --git a/internal/core/metadata/application/device_test.go b/internal/core/metadata/application/notify_test.go similarity index 54% rename from internal/core/metadata/application/device_test.go rename to internal/core/metadata/application/notify_test.go index f9884b20e3..ff0cda2513 100644 --- a/internal/core/metadata/application/device_test.go +++ b/internal/core/metadata/application/notify_test.go @@ -1,5 +1,6 @@ // // Copyright (C) 2022 Intel +// Copyright (C) 2023 Intel // // SPDX-License-Identifier: Apache-2.0 @@ -14,8 +15,9 @@ import ( "github.com/edgexfoundry/edgex-go/internal/core/metadata/config" "github.com/edgexfoundry/edgex-go/internal/core/metadata/container" - bootstrapConfig "github.com/edgexfoundry/go-mod-bootstrap/v3/config" mocks2 "github.com/edgexfoundry/go-mod-core-contracts/v3/clients/logger/mocks" + "github.com/edgexfoundry/go-mod-core-contracts/v3/common" + "github.com/edgexfoundry/go-mod-core-contracts/v3/dtos" "github.com/edgexfoundry/go-mod-core-contracts/v3/errors" "github.com/edgexfoundry/go-mod-messaging/v3/messaging/mocks" "github.com/edgexfoundry/go-mod-messaging/v3/pkg/types" @@ -25,18 +27,27 @@ import ( "github.com/stretchr/testify/require" bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/container" + bootstrapConfig "github.com/edgexfoundry/go-mod-bootstrap/v3/config" "github.com/edgexfoundry/go-mod-bootstrap/v3/di" - "github.com/edgexfoundry/go-mod-core-contracts/v3/common" - "github.com/edgexfoundry/go-mod-core-contracts/v3/dtos" - "github.com/edgexfoundry/go-mod-core-contracts/v3/models" ) -func TestPublishDeviceSystemEvent(t *testing.T) { - expectedDevice := models.Device{ - Name: "Camera-Device", +func TestPublishSystemEvent(t *testing.T) { + TestDeviceProfileName := "onvif-camera" + TestDeviceServiceName := "Device-onvif-camera" + TestDeviceName := "Camera-Device" + + expectedDevice := dtos.Device{ + Name: TestDeviceName, Id: uuid.NewString(), - ServiceName: "Device-onvif-camera", - ProfileName: "onvif-camera", + ServiceName: TestDeviceServiceName, + ProfileName: TestDeviceProfileName, + } + + expectedDeviceProfile := dtos.DeviceProfile{ + DeviceProfileBasicInfo: dtos.DeviceProfileBasicInfo{ + Id: uuid.NewString(), + Name: TestDeviceProfileName, + }, } expectedCorrelationID := uuid.NewString() @@ -44,18 +55,23 @@ func TestPublishDeviceSystemEvent(t *testing.T) { tests := []struct { Name string + Type string Action string PubError bool ClientMissing bool }{ - {"Device Add", common.DeviceSystemEventActionAdd, false, false}, - {"Device Update", common.DeviceSystemEventActionUpdate, false, false}, - {"Device Delete", common.DeviceSystemEventActionDelete, false, false}, - {"Client Missing Error", common.DeviceSystemEventActionAdd, false, true}, - {"Publish Error", common.DeviceSystemEventActionAdd, true, false}, + {"Device Add", common.DeviceSystemEventType, common.SystemEventActionAdd, false, false}, + {"Device Update", common.DeviceSystemEventType, common.SystemEventActionUpdate, false, false}, + {"Device Delete", common.DeviceSystemEventType, common.SystemEventActionDelete, false, false}, + {"Device Profile Add", common.DeviceProfileSystemEventType, common.SystemEventActionAdd, false, false}, + {"Device Profile Update", common.DeviceProfileSystemEventType, common.SystemEventActionUpdate, false, false}, + {"Device Profile Delete", common.DeviceProfileSystemEventType, common.SystemEventActionDelete, false, false}, + {"Client Missing Error", common.DeviceSystemEventType, common.SystemEventActionAdd, false, true}, + {"Publish Error", common.DeviceSystemEventType, common.SystemEventActionAdd, true, false}, } pubErrMsg := errors.NewCommonEdgeXWrapper(goErrors.New("publish failed")) + mockLogger := &mocks2.LoggingClient{} dic := di.NewContainer(di.ServiceConstructorMap{ container.ConfigurationName: func(get di.Get) interface{} { @@ -67,6 +83,9 @@ func TestPublishDeviceSystemEvent(t *testing.T) { }, } }, + bootstrapContainer.LoggingClientInterfaceName: func(get di.Get) interface{} { + return mockLogger + }, }) for _, test := range tests { @@ -79,37 +98,46 @@ func TestPublishDeviceSystemEvent(t *testing.T) { err := json.Unmarshal(envelope.Payload, &systemEvent) require.NoError(t, err) + switch test.Type { + case common.DeviceSystemEventType: + actualDevice := dtos.Device{} + err = systemEvent.DecodeDetails(&actualDevice) + require.NoError(t, err) + assert.Equal(t, expectedDevice.Name, actualDevice.Name) + assert.Equal(t, expectedDevice.Id, actualDevice.Id) + assert.Equal(t, expectedDevice.ServiceName, actualDevice.ServiceName) + assert.Equal(t, expectedDevice.ProfileName, actualDevice.ProfileName) + assert.Equal(t, expectedDevice.ServiceName, systemEvent.Owner) + case common.DeviceProfileSystemEventType: + actualDeviceProfile := dtos.DeviceProfile{} + err = systemEvent.DecodeDetails(&actualDeviceProfile) + require.NoError(t, err) + assert.Equal(t, expectedDeviceProfile.Name, actualDeviceProfile.Name) + assert.Equal(t, expectedDeviceProfile.Id, actualDeviceProfile.Id) + assert.Equal(t, common.CoreMetaDataServiceKey, systemEvent.Owner) + } + assert.Equal(t, common.ApiVersion, systemEvent.ApiVersion) - assert.Equal(t, common.DeviceSystemEventType, systemEvent.Type) + assert.Equal(t, test.Type, systemEvent.Type) assert.Equal(t, test.Action, systemEvent.Action) assert.Equal(t, common.CoreMetaDataServiceKey, systemEvent.Source) - assert.Equal(t, expectedDevice.ServiceName, systemEvent.Owner) assert.NotZero(t, systemEvent.Timestamp) - actualDevice := dtos.Device{} - err = systemEvent.DecodeDetails(&actualDevice) - require.NoError(t, err) - - assert.Equal(t, expectedDevice.Name, actualDevice.Name) - assert.Equal(t, expectedDevice.Id, actualDevice.Id) - assert.Equal(t, expectedDevice.ServiceName, actualDevice.ServiceName) - assert.Equal(t, expectedDevice.ProfileName, actualDevice.ProfileName) return nil } mockClient := &mocks.MessageClient{} - mockLogger := &mocks2.LoggingClient{} - mockLogger.On("Debugf", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return() + mockLogger.On("Debugf", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return() if test.PubError { mockClient.On("Publish", mock.Anything, mock.Anything).Return(pubErrMsg) - mockLogger.On("Errorf", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return() + mockLogger.On("Errorf", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return() } else { mockClient.On("Publish", mock.Anything, mock.Anything).Return(validatePublishCallFunc) } if test.ClientMissing { - mockLogger.On("Errorf", mock.Anything, mock.Anything).Return() + mockLogger.On("Errorf", mock.Anything, mock.Anything, mock.Anything).Return() dic.Update(di.ServiceConstructorMap{ bootstrapContainer.MessagingClientName: func(get di.Get) interface{} { return nil @@ -130,25 +158,35 @@ func TestPublishDeviceSystemEvent(t *testing.T) { // lint:ignore SA1029 legacy // nolint:staticcheck // See golangci-lint #741 ctx = context.WithValue(ctx, common.CorrelationHeader, expectedCorrelationID) + var expectedOwner string + var expectedDetails any + switch test.Type { + case common.DeviceSystemEventType: + expectedOwner = expectedDevice.ServiceName + expectedDetails = expectedDevice + case common.DeviceProfileSystemEventType: + expectedOwner = common.CoreMetaDataServiceKey + expectedDetails = expectedDeviceProfile + } - publishDeviceSystemEvent(test.Action, expectedDevice.ServiceName, expectedDevice, ctx, mockLogger, dic) + publishSystemEvent(test.Type, test.Action, expectedOwner, expectedDetails, ctx, dic) if test.ClientMissing { - mockLogger.AssertCalled(t, "Errorf", mock.Anything, noMessagingClientError) + mockLogger.AssertCalled(t, "Errorf", mock.Anything, mock.Anything, noMessagingClientError) return } expectedTopic := fmt.Sprintf("%s/%s/%s/%s/%s/%s", expectedPublishTopicPrefix, common.CoreMetaDataServiceKey, - common.DeviceSystemEventType, + test.Type, test.Action, - expectedDevice.ServiceName, + expectedOwner, expectedDevice.ProfileName) mockClient.AssertCalled(t, "Publish", mock.Anything, expectedTopic) if test.PubError { - mockLogger.AssertCalled(t, "Errorf", mock.Anything, mock.Anything, mock.Anything, mock.Anything, pubErrMsg) + mockLogger.AssertCalled(t, "Errorf", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, pubErrMsg) } }) } diff --git a/internal/core/metadata/controller/http/devicecommand.go b/internal/core/metadata/controller/http/devicecommand.go index e02dbbc109..eef7f7a8da 100644 --- a/internal/core/metadata/controller/http/devicecommand.go +++ b/internal/core/metadata/controller/http/devicecommand.go @@ -131,7 +131,7 @@ func (dc *DeviceCommandController) DeleteDeviceCommandByName(w http.ResponseWrit profileName := vars[common.Name] commandName := vars[common.CommandName] - err := application.DeleteDeviceCommandByName(profileName, commandName, dc.dic) + err := application.DeleteDeviceCommandByName(profileName, commandName, ctx, dc.dic) if err != nil { utils.WriteErrorResponse(w, ctx, lc, err, "") return diff --git a/internal/core/metadata/controller/http/deviceprofile_test.go b/internal/core/metadata/controller/http/deviceprofile_test.go index e9108740d1..503d9f50cb 100644 --- a/internal/core/metadata/controller/http/deviceprofile_test.go +++ b/internal/core/metadata/controller/http/deviceprofile_test.go @@ -491,6 +491,7 @@ func TestUpdateDeviceProfile(t *testing.T) { dbClientMock.On("DeviceCountByProfileName", deviceProfileModel.Name).Return(uint32(1), nil) dbClientMock.On("DevicesByProfileName", 0, -1, deviceProfileModel.Name).Return([]models.Device{{ServiceName: testDeviceServiceName}}, nil) dbClientMock.On("DeviceServiceByName", testDeviceServiceName).Return(models.DeviceService{}, nil) + dbClientMock.On("DeviceProfileByName", deviceProfileModel.Name).Return(deviceProfileModel, nil) dic.Update(di.ServiceConstructorMap{ container.DBClientInterfaceName: func(get di.Get) interface{} { return dbClientMock @@ -914,6 +915,7 @@ func TestUpdateDeviceProfileByYaml(t *testing.T) { dbClientMock.On("DeviceCountByProfileName", validDeviceProfileModel.Name).Return(uint32(1), nil) dbClientMock.On("DevicesByProfileName", 0, -1, validDeviceProfileModel.Name).Return([]models.Device{{ServiceName: testDeviceServiceName}}, nil) dbClientMock.On("DeviceServiceByName", testDeviceServiceName).Return(models.DeviceService{}, nil) + dbClientMock.On("DeviceProfileByName", validDeviceProfileModel.Name).Return(validDeviceProfileModel, nil) dic.Update(di.ServiceConstructorMap{ container.DBClientInterfaceName: func(get di.Get) interface{} { return dbClientMock @@ -1080,6 +1082,7 @@ func TestDeleteDeviceProfileByName(t *testing.T) { dbClientMock.On("DeleteDeviceProfileByName", provisionWatcherExists).Return(errors.NewCommonEdgeX( errors.KindStatusConflict, "fail to delete the device profile when associated provisionWatcher exists", nil)) dbClientMock.On("ProvisionWatchersByProfileName", 0, 1, provisionWatcherExists).Return([]models.ProvisionWatcher{models.ProvisionWatcher{}}, nil) + dbClientMock.On("DeviceProfileByName", mock.Anything).Return(models.DeviceProfile{}, nil) dic.Update(di.ServiceConstructorMap{ container.DBClientInterfaceName: func(get di.Get) interface{} { return dbClientMock diff --git a/internal/core/metadata/controller/http/deviceresource.go b/internal/core/metadata/controller/http/deviceresource.go index 124b4da5c4..e439e87b54 100644 --- a/internal/core/metadata/controller/http/deviceresource.go +++ b/internal/core/metadata/controller/http/deviceresource.go @@ -154,7 +154,7 @@ func (dc *DeviceResourceController) DeleteDeviceResourceByName(w http.ResponseWr profileName := vars[common.Name] resourceName := vars[common.ResourceName] - err := application.DeleteDeviceResourceByName(profileName, resourceName, dc.dic) + err := application.DeleteDeviceResourceByName(profileName, resourceName, ctx, dc.dic) if err != nil { utils.WriteErrorResponse(w, ctx, lc, err, "") return