diff --git a/snap/README.md b/snap/README.md new file mode 100644 index 00000000..d2d388bc --- /dev/null +++ b/snap/README.md @@ -0,0 +1,168 @@ +# EdgeX SNMP Device Service Snap + +[![snap store badge](https://raw.githubusercontent.com/snapcore/snap-store-badges/master/EN/%5BEN%5D-snap-store-black-uneditable.png)](https://snapcraft.io/edgex-device-snmp) + +This folder contains snap packaging for the EdgeX SNMP Protocol Device Service Snap + +The snap currently supports both `amd64` and `arm64` platforms + +## Installation + +### Installing snapd + +The snap can be installed on any system that supports snaps. You can see how to install snaps on your system [here](https://snapcraft.io/docs/installing-snapd/6735). + +However for full security confinement, the snap should be installed on an Ubuntu 18.04 LTS or later (Desktop or Server), or a system running Ubuntu Core 18 or later. + +### Installing EdgeX Device SNMP as a snap + +The snap is published in the snap store at https://snapcraft.io/edgex-device-snmp. You can see the current revisions available for your machine's architecture by running the command: + +``` +$ snap info edgex-device-snmp +``` + +The latest stable version of the snap can be installed using: + +``` +$ sudo snap install edgex-device-snmp +``` + +A specific release of the snap can be installed from a dedicated channel. For example, to install the 2.1 (Jakarta) release: + +``` +$ sudo snap install edgex-device-snmp --channel=2.1 +``` + +The latest development version of the snap can be installed using: + +``` +$ sudo snap install edgex-device-snmp --edge +``` + +**Note** - the snap has only been tested on Ubuntu Core, Desktop, and Server. + +## Snap configuration + +Device services implement a service dependency check on startup which ensures that all of the runtime dependencies of a particular service are met before the service transitions to active state. + +Snapd doesn't support orchestration between services in different snaps. It is therefore possible on a reboot for a device service to come up faster than all of the required services running in the main edgexfoundry snap. If this happens, it's possible that the device service repeatedly fails startup, and if it exceeds the systemd default limits, then it might be left in a failed state. This situation might be more likely on constrained hardware (e.g. RPi). + +This snap therefore implements a basic retry loop with a maximum duration and sleep interval. If the dependent services are not available, the service sleeps for the defined interval (default: 1s) and then tries again up to a maximum duration (default: 60s). These values can be overridden with the following commands: + +To change the maximum duration, use the following command: + +``` +$ sudo snap set edgex-device-snmp startup-duration=60 +``` + +To change the interval between retries, use the following command: + +``` +$ sudo snap set edgex-device-snmp startup-interval=1 +``` + +The service can then be started as follows. The "--enable" option ensures that as well as starting the service now, it will be automatically started on boot: + +``` +$ sudo snap start --enable edgex-device-snmp.device-snmp +``` + +### Using a content interface to set device configuration + +The `device-config` content interface allows another snap to seed this device +snap with configuration files under the `$SNAP_DATA/config/device-snmp/res` directory. + +Note that the `device-config` content interface does NOT support seeding of the Secret Store Token because that file is expected at a different path. + +To use, create a new snap with a directory containing the configuration files. Your snapcraft.yaml file then needs to define a slot with read access to the directory you are sharing. + +``` +slots: + device-config: + interface: content + content: device-config + read: + - $SNAP/config +``` + +where `$SNAP/config` is configuration directory your snap is providing to the device snap. + +Then connect the plug in the device snap to the slot in your snap, which will replace the configuration in the device snap. Do this with: + +``` +$ sudo snap connect edgex-device-snmp:device-config your-snap:device-config +``` + +This needs to be done before the device service is started for the first time. Once you have set the configuration the device service can be started and it will then be configured using the settings you provided: + +``` +$ sudo snap start edgex-device-snmp.device-snmp +``` + +**Note** - content interfaces from snaps installed from the Snap Store that have the same publisher connect automatically. For more information on snap content interfaces please refer to the snapcraft.io [Content Interface](https://snapcraft.io/docs/content-interface) documentation. + +### Autostart + +By default, the edgex-device-snmp disables its service on install, as the expectation is that the default profile configuration files will be customized, and thus this behavior allows the profile `configuration.toml` files in $SNAP_DATA to be modified before the service is first started. + +This behavior can be overridden by setting the `autostart` configuration setting to "true". This is useful when configuration and/or device profiles are being provided via configuration or gadget snap content interface. + +**Note** - this option is typically set from a gadget snap. + +### Rich Configuration + +While it's possible on Ubuntu Core to provide additional profiles via gadget snap content interface, quite often only minor changes to existing profiles are required. + +These changes can be accomplished via support for EdgeX environment variable configuration overrides via the snap's configure hook. If the service has already been started, setting one of these overrides currently requires the service to be restarted via the command-line or snapd's REST API. If the overrides are provided via the snap configuration defaults capability of a gadget snap, the overrides will be picked up when the services are first started. + +The following syntax is used to specify service-specific configuration overrides: + +``` +env.. +``` + +For instance, to setup an override of the service's Port use: + +``` +$ sudo snap set edgex-device-snmp env.service.port=2112 +``` + +And restart the service: + +``` +$ sudo snap restart edgex-device-snmp.device-snmp +``` + +**Note** - at this time changes to configuration values in the [Writable] section are not supported. For details on the mapping of configuration options to Config options, please refer to "Service Environment Configuration Overrides". + +## Service Environment Configuration Overrides + +**Note** - all of the configuration options below must be specified with the prefix: `env.` + +``` +[Service] +service.health-check-interval // Service.HealthCheckInterval +service.host // Service.Host +service.server-bind-addr // Service.ServerBindAddr +service.port // Service.Port +service.max-result-count // Service.MaxResultCount +service.max-request-size // Service.MaxRequestSize +service.startup-msg // Service.StartupMsg +service.request-timeout // Service.RequestTimeout + +[SecretStore] +secret-store.secrets-file // SecretStore.SecretsFile +secret-store.disable-scrub-secrets-file // SecretStore.DisableScrubSecretsFile + +[Clients.core-data] +clients.core-data.port // Clients.core-data.Port + +[Clients.core-metadata] +clients.core-metadata.port // Clients.core-metadata.Port + +[Device] +device.update-last-connected // Device.UpdateLastConnected +device.use-message-bus // Device.UseMessageBus +``` + diff --git a/snap/local/assets/edgex-snap-icon.png b/snap/local/assets/edgex-snap-icon.png new file mode 100644 index 00000000..c67e89df Binary files /dev/null and b/snap/local/assets/edgex-snap-icon.png differ diff --git a/snap/local/hooks/Makefile b/snap/local/hooks/Makefile new file mode 100644 index 00000000..071f433b --- /dev/null +++ b/snap/local/hooks/Makefile @@ -0,0 +1,16 @@ +HOOKS=cmd/configure/configure cmd/install/install + +tidy: + go mod tidy + +build: tidy $(HOOKS) + +cmd/configure/configure: + go build -o $@ ./cmd/configure + +cmd/install/install: + go build -o $@ ./cmd/install + +clean: + rm -f $(HOOKS) + diff --git a/snap/local/hooks/cmd/configure/configure.go b/snap/local/hooks/cmd/configure/configure.go new file mode 100755 index 00000000..bf7084f8 --- /dev/null +++ b/snap/local/hooks/cmd/configure/configure.go @@ -0,0 +1,102 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2021 Canonical Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * + * SPDX-License-Identifier: Apache-2.0' + */ + +package main + +import ( + "fmt" + "os" + "strings" + + hooks "github.com/canonical/edgex-snap-hooks/v2" + local "github.com/edgexfoundry/device-snmp-go/hooks" +) + +var cli *hooks.CtlCli = hooks.NewSnapCtl() + +func main() { + var debug = false + var err error + var envJSON string + + status, err := cli.Config("debug") + if err != nil { + fmt.Println(fmt.Sprintf("edgex-device-snmp:configure: can't read value of 'debug': %v", err)) + os.Exit(1) + } + if status == "true" { + debug = true + } + + if err = hooks.Init(debug, "edgex-device-snmp"); err != nil { + fmt.Println(fmt.Sprintf("edgex-device-snmp:configure: initialization failure: %v", err)) + os.Exit(1) + + } + + // read env var override configuration + envJSON, err = cli.Config(hooks.EnvConfig) + if err != nil { + hooks.Error(fmt.Sprintf("Reading config 'env' failed: %v", err)) + os.Exit(1) + } + + if envJSON != "" { + hooks.Debug(fmt.Sprintf("edgex-device-snmp:configure: envJSON: %s", envJSON)) + err = hooks.HandleEdgeXConfig("device-snmp", envJSON, local.ConfToEnv) + if err != nil { + hooks.Error(fmt.Sprintf("HandleEdgeXConfig failed: %v", err)) + os.Exit(1) + } + } + + // If autostart is not explicitly set, default to "no" + // as only example service configuration and profiles + // are provided by default. + autostart, err := cli.Config(hooks.AutostartConfig) + if err != nil { + hooks.Error(fmt.Sprintf("Reading config 'autostart' failed: %v", err)) + os.Exit(1) + } + if autostart == "" { + hooks.Debug("edgex-device-snmp autostart is NOT set, initializing to 'no'") + autostart = "no" + } + autostart = strings.ToLower(autostart) + + hooks.Debug(fmt.Sprintf("edgex-device-snmp autostart is %s", autostart)) + + // service is stopped/disabled by default in the install hook + switch autostart { + case "true": + fallthrough + case "yes": + err = cli.Start("device-snmp", true) + if err != nil { + hooks.Error(fmt.Sprintf("Can't start service - %v", err)) + os.Exit(1) + } + case "false": + // no action necessary + case "no": + // no action necessary + default: + hooks.Error(fmt.Sprintf("Invalid value for 'autostart' : %s", autostart)) + os.Exit(1) + } +} diff --git a/snap/local/hooks/cmd/install/install.go b/snap/local/hooks/cmd/install/install.go new file mode 100755 index 00000000..6d94d7b1 --- /dev/null +++ b/snap/local/hooks/cmd/install/install.go @@ -0,0 +1,116 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2021 Canonical Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * + * SPDX-License-Identifier: Apache-2.0' + */ + +package main + +import ( + "fmt" + "os" + "path/filepath" + + hooks "github.com/canonical/edgex-snap-hooks/v2" +) + +var cli *hooks.CtlCli = hooks.NewSnapCtl() + +// installProfiles copies the profile configuration.toml files from $SNAP to $SNAP_DATA. +func installConfig() error { + var err error + + path := "/config/device-snmp/res/configuration.toml" + destFile := hooks.SnapData + path + srcFile := hooks.Snap + path + + if err = os.MkdirAll(filepath.Dir(destFile), 0755); err != nil { + return err + } + + if err = hooks.CopyFile(srcFile, destFile); err != nil { + return err + } + + return nil +} + +func installDevices() error { + var err error + + path := "/config/device-snmp/res/devices/device.snmp.trendnet.TPE082WS.toml" + destFile := hooks.SnapData + path + srcFile := hooks.Snap + path + + if err = os.MkdirAll(filepath.Dir(destFile), 0755); err != nil { + return err + } + + if err = hooks.CopyFile(srcFile, destFile); err != nil { + return err + } + + return nil +} + +func installDevProfiles() error { + var err error + + profs := [...]string{"patlite", "switch.dell.N1108P-ON", "trendnet.TPE082WS"} + + for _, v := range profs { + path := fmt.Sprintf("/config/device-snmp/res/profiles/device.snmp.%s.yaml", v) + destFile := hooks.SnapData + path + srcFile := hooks.Snap + path + + if err := os.MkdirAll(filepath.Dir(destFile), 0755); err != nil { + return err + } + + if err = hooks.CopyFile(srcFile, destFile); err != nil { + return err + } + } + + return nil +} + +func main() { + var err error + + if err = hooks.Init(false, "edgex-device-snmp"); err != nil { + fmt.Println(fmt.Sprintf("edgex-device-snmp::install: initialization failure: %v", err)) + os.Exit(1) + } + + err = installConfig() + if err != nil { + hooks.Error(fmt.Sprintf("edgex-device-snmp:install: %v", err)) + os.Exit(1) + } + + err = installDevices() + if err != nil { + hooks.Error(fmt.Sprintf("edgex-device-snmp:install: %v", err)) + os.Exit(1) + } + + err = installDevProfiles() + if err != nil { + hooks.Error(fmt.Sprintf("edgex-device-snmp:install: %v", err)) + os.Exit(1) + } + +} diff --git a/snap/local/hooks/const.go b/snap/local/hooks/const.go new file mode 100644 index 00000000..e4d13b95 --- /dev/null +++ b/snap/local/hooks/const.go @@ -0,0 +1,33 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2021 Canonical Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * + * SPDX-License-Identifier: Apache-2.0' + */ + +package hooks + +// ConfToEnv defines mappings from snap config keys to EdgeX environment variable +// names that are used to override individual device-snmp's [Driver] configuration +// values via a .env file read by the snap service wrapper. +// +// The syntax to set a configuration key is: +// +// env.
. +// +var ConfToEnv = map[string]string{ + // [Device] + "device.update-last-connected": "DEVICE_UPDATELASTCONNECTED", + "device.use-message-bus": "DEVICE_USEMESSAGEBUS", +} diff --git a/snap/local/hooks/go.mod b/snap/local/hooks/go.mod new file mode 100644 index 00000000..45957bf7 --- /dev/null +++ b/snap/local/hooks/go.mod @@ -0,0 +1,6 @@ +module github.com/edgexfoundry/device-snmp-go/hooks + +require github.com/canonical/edgex-snap-hooks/v2 v2.0.7 + +go 1.16 + diff --git a/snap/local/runtime-helpers/bin/startup-env-var.sh b/snap/local/runtime-helpers/bin/startup-env-var.sh new file mode 100755 index 00000000..97c13d5e --- /dev/null +++ b/snap/local/runtime-helpers/bin/startup-env-var.sh @@ -0,0 +1,31 @@ +#!/bin/bash -ex + +EDGEX_STARTUP_DURATION=$(snapctl get startup-duration) + +if [ -n "$EDGEX_STARTUP_DURATION" ]; then + export EDGEX_STARTUP_DURATION +fi + +EDGEX_STARTUP_INTERVAL=$(snapctl get startup-interval) + +if [ -n "$EDGEX_STARTUP_INTERVAL" ]; then + export EDGEX_STARTUP_INTERVAL +fi + +# convert cmdline to string array +ARGV=($@) + +# grab binary path +BINPATH="${ARGV[0]}" + +# binary name == service name/key +SERVICE=$(basename "$BINPATH") +SERVICE_ENV="$SNAP_DATA/config/$SERVICE/res/$SERVICE.env" + +if [ -f "$SERVICE_ENV" ]; then + logger "edgex service override: : sourcing $SERVICE_ENV" + source "$SERVICE_ENV" +fi + +exec "$@" + diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml new file mode 100644 index 00000000..347b934e --- /dev/null +++ b/snap/snapcraft.yaml @@ -0,0 +1,103 @@ +name: edgex-device-snmp +base: core20 +license: Apache-2.0 +adopt-info: device-snmp +summary: Connect network switches to EdgeX using device-snmp +title: EdgeX snmp Device Service +description: | + This device service provides network switches configuration, sending and + receiving event readings from switches. Currently, this device service + is tailored specifically to the TrendNET TEG-082WS switch. + +# TODO: add armhf when the project supports this +architectures: + - build-on: amd64 + - build-on: arm64 + +grade: stable +confinement: strict + +icon: snap/local/assets/edgex-snap-icon.png + +# edinburgh, geneva,hanoi = 1, ireland,jakarta = 2 +epoch: 2 + +slots: + edgex-secretstore-token: + interface: content + content: edgex-secretstore-token + source: + write: [$SNAP_DATA/device-snmp] + +apps: + device-snmp: + adapter: full + command: bin/device-snmp -confdir $SNAP_DATA/config/device-snmp -profile res --registry $CONSUL_ADDR + command-chain: + - bin/startup-env-var.sh + environment: + CONSUL_ADDR: "consul://localhost:8500" + DEVICE_PROFILESDIR: $SNAP_DATA/config/device-snmp/res/profiles + DEVICE_DEVICESDIR: $SNAP_DATA/config/device-snmp/res/devices + SECRETSTORE_TOKENFILE: $SNAP_DATA/device-snmp/secrets-token.json + WRITABLE_LOGLEVEL: 'INFO' + daemon: simple + passthrough: + install-mode: disable + plugs: [network, network-bind] + +plugs: + device-config: + interface: content + content: device-config + target: $SNAP_DATA/config/device-snmp/res + +parts: + hooks: + source: snap/local/hooks + plugin: make + build-snaps: [go/1.16/stable] + override-build: | + cd $SNAPCRAFT_PART_SRC + make build + install -DT ./cmd/configure/configure $SNAPCRAFT_PART_INSTALL/snap/hooks/configure + install -DT ./cmd/install/install $SNAPCRAFT_PART_INSTALL/snap/hooks/install + + device-snmp: + source: . + plugin: make + build-packages: [git, libzmq3-dev, zip, pkg-config] + stage-packages: [libzmq5] + build-snaps: + - go/1.16/stable + override-build: | + cd $SNAPCRAFT_PART_SRC + + GIT_VERSION=$(git describe --tags --abbrev=0 | sed 's/v//') + if [ -z "$GIT_VERSION" ]; then + GIT_VERSION="0.0.0" + fi + snapcraftctl set-version ${GIT_VERSION} + # this is needed for make build + echo $GIT_VERSION > ./VERSION + + go mod tidy + make build + + install -DT "./cmd/device-snmp" "$SNAPCRAFT_PART_INSTALL/bin/device-snmp" + + # copy all config files + mkdir -p $SNAPCRAFT_PART_INSTALL/config/device-snmp/res + cp -rv cmd/res/configuration.toml $SNAPCRAFT_PART_INSTALL/config/device-snmp/res/configuration.toml + cp -rv cmd/res/devices $SNAPCRAFT_PART_INSTALL/config/device-snmp/res/devices + cp -rv cmd/res/profiles $SNAPCRAFT_PART_INSTALL/config/device-snmp/res/profiles + + install -DT "./Attribution.txt" \ + "$SNAPCRAFT_PART_INSTALL/usr/share/doc/device-snmp/Attribution.txt" + install -DT "./LICENSE" \ + "$SNAPCRAFT_PART_INSTALL/usr/share/doc/device-snmp/LICENSE" + + config-common: + plugin: dump + source: snap/local/runtime-helpers +