From 1f95b192d61f5c14ee494a45b5556a6764ee66eb Mon Sep 17 00:00:00 2001 From: Chris Hung Date: Mon, 9 Jan 2023 11:48:30 +0800 Subject: [PATCH] feat: implement support for ProvisionWatchersDir add 'ProvisionWatchersDir' configuration to support adding provision watchers during device service startup. closes #1100 Signed-off-by: Chris Hung --- example/README.md | 5 +- .../cmd/device-simple/res/configuration.toml | 1 + .../device-simple/res/provisionwatcher.json | 31 ------- .../Simple-Provision-Watcher.json | 26 ++++++ internal/config/types.go | 4 +- internal/provision/provisionwatchers.go | 90 +++++++++++++++++++ pkg/service/init.go | 11 ++- 7 files changed, 131 insertions(+), 37 deletions(-) delete mode 100644 example/cmd/device-simple/res/provisionwatcher.json create mode 100644 example/cmd/device-simple/res/provisionwatchers/Simple-Provision-Watcher.json create mode 100644 internal/provision/provisionwatchers.go diff --git a/example/README.md b/example/README.md index d4ecb798d..0dbcf0851 100644 --- a/example/README.md +++ b/example/README.md @@ -36,6 +36,5 @@ Dynamic Device Discovery is triggered either by internal timer(see `Device/Disco The following steps show how to trigger discovery on device-simple: 1. Set `Device/Discovery/Enabled` to true in [configuration file](cmd/device-simple/res/configuration.toml) -2. Post the [provided provisionwatcher](cmd/device-simple/res/provisionwatcher.json) into core-metadata endpoint: http://edgex-core-metadata:59881/api/v2/provisionwatcher -3. Trigger discovery by sending POST request to DS endpoint: http://edgex-device-simple:59999/api/v2/discovery -4. `Simple-Device02` will be discovered and added to EdgeX. \ No newline at end of file +2. Trigger discovery by sending POST request to DS endpoint: http://edgex-device-simple:59999/api/v2/discovery +3. `Simple-Device02` will be discovered and added to EdgeX. \ No newline at end of file diff --git a/example/cmd/device-simple/res/configuration.toml b/example/cmd/device-simple/res/configuration.toml index b3b53d3e8..92ba129fc 100644 --- a/example/cmd/device-simple/res/configuration.toml +++ b/example/cmd/device-simple/res/configuration.toml @@ -118,6 +118,7 @@ TokenFile = "/tmp/edgex/secrets/device-simple/secrets-token.json" MaxCmdValueLen = 256 ProfilesDir = "./res/profiles" DevicesDir = "./res/devices" + ProvisionWatchersDir = "./res/provisionwatchers" UpdateLastConnected = false AsyncBufferSize = 1 EnableAsyncReadings = true diff --git a/example/cmd/device-simple/res/provisionwatcher.json b/example/cmd/device-simple/res/provisionwatcher.json deleted file mode 100644 index fdaf1c86b..000000000 --- a/example/cmd/device-simple/res/provisionwatcher.json +++ /dev/null @@ -1,31 +0,0 @@ -[ - { - "apiVersion": "v2", - "provisionWatcher": { - "name": "Simple-Provision-Watcher", - "labels": [ - "simple" - ], - "identifiers": { - "Address":"simple[0-9]+", - "Port":"3[0-9]{2}" - }, - "blockingIdentifiers": { - "Port": [ - "397", - "398", - "399" - ] - }, - "profileName": "Simple-Device", - "serviceName": "device-simple", - "adminState": "UNLOCKED", - "autoEvents": [ - { - "interval": "15s", - "sourceName": "SwitchButton" - } - ] - } - } -] diff --git a/example/cmd/device-simple/res/provisionwatchers/Simple-Provision-Watcher.json b/example/cmd/device-simple/res/provisionwatchers/Simple-Provision-Watcher.json new file mode 100644 index 000000000..55e31f72e --- /dev/null +++ b/example/cmd/device-simple/res/provisionwatchers/Simple-Provision-Watcher.json @@ -0,0 +1,26 @@ +{ + "name": "Simple-Provision-Watcher", + "labels": [ + "simple" + ], + "identifiers": { + "Address":"simple[0-9]+", + "Port":"3[0-9]{2}" + }, + "blockingIdentifiers": { + "Port": [ + "397", + "398", + "399" + ] + }, + "profileName": "Simple-Device", + "serviceName": "device-simple", + "adminState": "UNLOCKED", + "autoEvents": [ + { + "interval": "15s", + "sourceName": "SwitchButton" + } + ] +} diff --git a/internal/config/types.go b/internal/config/types.go index 26a3e360f..561300248 100644 --- a/internal/config/types.go +++ b/internal/config/types.go @@ -1,7 +1,7 @@ // -*- mode: Go; indent-tabs-mode: t -*- // // Copyright (C) 2017-2018 Canonical Ltd -// Copyright (C) 2018-2021 IOTech Ltd +// Copyright (C) 2018-2023 IOTech Ltd // Copyright (c) 2021 Intel Corporation // // SPDX-License-Identifier: Apache-2.0 @@ -44,6 +44,8 @@ type DeviceInfo struct { ProfilesDir string // DevicesDir specifies a directory contains devices files which should be imported on startup. DevicesDir string + // ProvisionWatchersDir specifies a directory contains provision watcher files which should be imported on startup. + ProvisionWatchersDir string // UpdateLastConnected specifies whether to update device's LastConnected // timestamp in metadata. UpdateLastConnected bool diff --git a/internal/provision/provisionwatchers.go b/internal/provision/provisionwatchers.go new file mode 100644 index 000000000..f7e3638ff --- /dev/null +++ b/internal/provision/provisionwatchers.go @@ -0,0 +1,90 @@ +// +// Copyright (C) 2023 IOTech Ltd +// +// SPDX-License-Identifier: Apache-2.0 + +package provision + +import ( + "context" + "encoding/json" + "os" + "path/filepath" + "strings" + + "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/google/uuid" + + "github.com/edgexfoundry/device-sdk-go/v3/internal/cache" +) + +func LoadProvisionWatchers(path string, dic *di.Container) errors.EdgeX { + if path == "" { + return nil + } + + absPath, err := filepath.Abs(path) + if err != nil { + return errors.NewCommonEdgeX(errors.KindServerError, "failed to create absolute path", err) + } + + files, err := os.ReadDir(absPath) + if err != nil { + return errors.NewCommonEdgeX(errors.KindServerError, "failed to read directory", err) + } + + if len(files) == 0 { + return nil + } + + lc := container.LoggingClientFrom(dic.Get) + lc.Infof("Loading pre-defined provision watchers from %s(%d files found)", absPath, len(files)) + + var addProvisionWatchersReq []requests.AddProvisionWatcherRequest + for _, file := range files { + var watcher dtos.ProvisionWatcher + filename := filepath.Join(absPath, file.Name()) + if !strings.HasSuffix(filename, jsonExt) { + continue + } + + data, err := os.ReadFile(filename) + if err != nil { + lc.Errorf("failed to read %s: %v", filename, err) + continue + } + + if err := json.Unmarshal(data, &watcher); err != nil { + lc.Errorf("failed to JSON decode %s: %v", filename, err) + continue + } + + err = common.Validate(watcher) + if err != nil { + lc.Errorf("ProvisionWatcher %s validation failed: %v", watcher.Name, err) + continue + } + + if _, ok := cache.ProvisionWatchers().ForName(watcher.Name); ok { + lc.Infof("ProvisionWatcher %s exists, using the existing one", watcher.Name) + } else { + lc.Infof("ProvisionWatcher %s not found in Metadata, adding it...", watcher.Name) + req := requests.NewAddProvisionWatcherRequest(watcher) + addProvisionWatchersReq = append(addProvisionWatchersReq, req) + } + } + + if len(addProvisionWatchersReq) == 0 { + return nil + } + + pwc := container.ProvisionWatcherClientFrom(dic.Get) + ctx := context.WithValue(context.Background(), common.CorrelationHeader, uuid.NewString()) //nolint: staticcheck + _, edgexErr := pwc.Add(ctx, addProvisionWatchersReq) + return edgexErr +} diff --git a/pkg/service/init.go b/pkg/service/init.go index 36d421003..ca363c17b 100644 --- a/pkg/service/init.go +++ b/pkg/service/init.go @@ -1,6 +1,6 @@ // -*- Mode: Go; indent-tabs-mode: t -*- // -// Copyright (C) 2020-2022 IOTech Ltd +// Copyright (C) 2020-2023 IOTech Ltd // // SPDX-License-Identifier: Apache-2.0 @@ -10,11 +10,12 @@ import ( "context" "sync" - "github.com/edgexfoundry/device-sdk-go/v3/internal/common" "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/startup" "github.com/edgexfoundry/go-mod-bootstrap/v3/di" "github.com/gorilla/mux" + "github.com/edgexfoundry/device-sdk-go/v3/internal/common" + "github.com/edgexfoundry/device-sdk-go/v3/internal/cache" "github.com/edgexfoundry/device-sdk-go/v3/internal/provision" "github.com/edgexfoundry/device-sdk-go/v3/pkg/models" @@ -78,6 +79,12 @@ func (b *Bootstrap) BootstrapHandler(ctx context.Context, wg *sync.WaitGroup, st return false } + err = provision.LoadProvisionWatchers(ds.config.Device.ProvisionWatchersDir, dic) + if err != nil { + ds.LoggingClient.Errorf("Failed to create the pre-defined provision watchers: %v", err) + return false + } + ds.manager.StartAutoEvents() // Very important that this handler is called after the NewServiceMetrics handler so