diff --git a/internal/driver/config.go b/internal/driver/config.go index a0d559ee..2ec5ef96 100644 --- a/internal/driver/config.go +++ b/internal/driver/config.go @@ -20,6 +20,9 @@ type CustomConfig struct { // MaxDiscoverDurationSeconds is the maximum amount of seconds for a discovery to run. It is important // to have this configured in the case of larger subnets such as /16 and /8 MaxDiscoverDurationSeconds int + + // Location of Provision Watchers + ProvisionWatcherDir string } // ServiceConfig a struct that wraps CustomConfig which holds the values for driver configuration diff --git a/internal/driver/driver.go b/internal/driver/driver.go index c4731f48..c0d64ef4 100644 --- a/internal/driver/driver.go +++ b/internal/driver/driver.go @@ -53,7 +53,6 @@ const ( // enable this by default, otherwise discovery will not work. registerProvisionWatchers = true - provisionWatcherFolder = "res/provision_watchers" // discoverDebounceDuration is the amount of time to wait for additional changes to discover // configuration before auto-triggering a discovery @@ -713,6 +712,13 @@ func getAddr(protocols protocolMap) (net.Addr, error) { } func (d *Driver) addProvisionWatchers() error { + + provisionWatcherFolder := driver.config.AppCustom.ProvisionWatcherDir + if provisionWatcherFolder == "" { + provisionWatcherFolder = "res/provision_watchers" + } + d.lc.Infof("Adding provision watchers from %s", provisionWatcherFolder) + files, err := ioutil.ReadDir(provisionWatcherFolder) if err != nil { return err diff --git a/snap/local/hooks/cmd/install/install.go b/snap/local/hooks/cmd/install/install.go index 816cb351..00cbf476 100644 --- a/snap/local/hooks/cmd/install/install.go +++ b/snap/local/hooks/cmd/install/install.go @@ -47,6 +47,28 @@ func installConfig() error { return nil } +func installProvisionWatchers() error { + var err error + + profs := [...]string{"impinj", "llrp"} + + for _, v := range profs { + path := fmt.Sprintf("/config/device-rfid-llrp/res/provision_watchers/%s.provision.watcher.json", 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 installDevices() error { //No device files @@ -101,4 +123,10 @@ func main() { hooks.Error(fmt.Sprintf("edgex-device-rfid-llrp:install: %v", err)) os.Exit(1) } + + err = installProvisionWatchers() + if err != nil { + hooks.Error(fmt.Sprintf("edgex-device-rfid-llrp:install: %v", err)) + os.Exit(1) + } } diff --git a/snap/local/runtime-helpers/bin/auto-configure.sh b/snap/local/runtime-helpers/bin/auto-configure.sh new file mode 100755 index 00000000..12d6568e --- /dev/null +++ b/snap/local/runtime-helpers/bin/auto-configure.sh @@ -0,0 +1,155 @@ +#!/usr/bin/env bash + +# +# Copyright (C) 2020-2021 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 +# + +# +# The purpose of this script is to make it easier for an end user to configure LLRP device discovery +# without the need to have knowledge about subnets and/or CIDR format. The "DiscoverySubnets" config +# option defaults to blank in the configuration.toml file, and needs to be provided before a discovery can occur. +# This allows the LLRP device service to be run in a NAT-ed environment without host-mode networking because the subnet information +# is user-provided and does not rely on the device-rfid-llrp service to detect it. +# +# Essentially how this script works is it polls the machine it is running on and finds the active subnet for +# any and all network interfaces that are on the machine which are physical (non-virtual) and online. +# It uses this information to automatically fill out the "DiscoverySubnets" configuration option through Consul of a deployed +# device-rfid-llrp instance. +# +# NOTE 1: This script requires EdgeX Consul and the device-rfid-llrp service to have been run before this +# script will function. +# +# NOTE 2: If the "DiscoverySubnets" config is provided via "configuration.toml" this script does +# not need to be run. +# + +set -eu + +# If ran with DEBUG=1, enable bash command tracing +DEBUG=${DEBUG:-0} +if [ "${DEBUG}" == "1" ]; then + set -x +fi + +# Snap-specific settings +# This script must run using snapcraft-runner, to set the shared libraries path correctly +# network-control and network-observe are required. +CURL=$SNAP/usr/bin/curl +CONSUL_TOKEN=$1 + + +spacing=18; prev_line="\e[1A\e[$((spacing + 2))C" +green="\e[32m"; red="\e[31m"; clear="\e[0m"; bold="\e[1m"; normal="\e[22;24m" + +trap 'err' ERR +err() { + echo -e "${red}${bold}Failed!${clear}" + exit 1 +} + +CONSUL_URL=${CONSUL_URL:-http://localhost:8500} +url="${CONSUL_URL}/v1/kv/edgex/devices/2.0/device-rfid-llrp/AppCustom/DiscoverySubnets" + + +### Dependencies Check +# Note: trailing ${red} is to colorize red all potential error output from the following commands +printf "${bold}%${spacing}s${clear}: ...\n${red}" "Dependencies Check" +if ! type -P curl >/dev/null; then + echo -e "${bold}${red}Failed!${normal} Please install ${bold}curl${normal} in order to use this script!${clear}" + exit 1 +fi +echo -e "${prev_line}${green}Success${clear}" + +### Consul Check +# Note: trailing ${red} is to colorize red all potential error output from the following commands +printf "${bold}%${spacing}s${clear}: ...\n${red}" "Consul Check" +echo "$CURL -X GET -H "X-Consul-Token:$CONSUL_TOKEN" -w "%{http_code}" -o /dev/null -s "${url}" || echo $?" +code=$($CURL -X GET -H "X-Consul-Token:$CONSUL_TOKEN" -w "%{http_code}" -o /dev/null -s "${url}" || echo $?) +if [ $((code)) -ne 200 ]; then + echo -e "${red}${bold}Failed!${normal} curl returned a status code of '${bold}$((code))'${normal}" + # Special message for error code 7 + if [ $((code)) -eq 7 ]; then + echo -e "* Error code '7' denotes 'Failed to connect to host or proxy'" + fi + # Error 404 means it connected to consul but couldn't find the key + if [ $((code)) -eq 404 ]; then + echo -e "* Have you deployed the ${bold}device-rfid-llrp${normal} service?${clear}" + else + echo -e "* Is Consul deployed and accessible?${clear}" + fi + exit $((code)) +fi +echo -e "${prev_line}${green}Success${clear}" + +### Detect Interfaces +printf "${bold}%${spacing}s${clear}: ...\n${red}" "Interfaces" +# find all online non-virtual network interfaces and print them separated by `|` for regex matching. +# example output: eno1|eno2|eno3|...| +# note: a trailing '|' is produced which is removed in a later step +ifaces=$( + find /sys/class/net -mindepth 1 -maxdepth 2 `# list all network interfaces` \ + -not -lname '*devices/virtual*' `# filter out all virtual interfaces` \ + -execdir grep -q 'up' "{}/operstate" \; `# ensure interface is online (operstate == up)` \ + -printf '%f|' `# print them separated by | for regex matching` +) +if [ -z "${ifaces}" ]; then + echo "Error, no online physical network interfaces detected.${clear}" + exit 1 +fi +echo -e "${prev_line}${clear}${ifaces//|/ }" + +### Detect Subnets +printf "${bold}%${spacing}s${clear}: ...\n${red}" "Subnets" +# print all ipv4 subnets, filter for just the ones associated with our physical interfaces, +# grab the unique ones and join them by commas +# +# sed -n followed by "s///p" means find and print (with replacements) only the lines containing a match +# '::-1' strips the last character (trailing |) +# 'eno1|eno2|' becomes "s/ dev (eno1|eno2).+//p" +# (eno1|eno2) is a matched group of possible values (| means OR) +# .+ is a catch all to prevent printing the rest of the line +# +# Example Input: +# 10.0.0.0/24 dev eno1 proto kernel src 10.0.0.212 metric 600 +# 192.168.1.0/24 dev eno2 proto kernel src 192.168.1.134 metric 900 +# 172.17.0.0/16 dev docker0 proto kernel src 172.17.0.1 linkdown +# +# Example Output: +# 10.0.0.0/24 +# 192.168.1.0/24 +# +# Explanation: +# - The first line matched the 'eno1' interface, so everything starting from " dev eno1 ..." +# is stripped out, and we are left with just the subnet (10.0.0.0/24). +# - The second line matched the 'eno2' interface, same process as before and we are left with just the subnet. +# - The third line does not match either interface and is not printed. + +subnets=$( + # Print all IPv4 routes, one per line + ip -4 -o route list scope link | + # Regex match it against all of our online physical interfaces + sed -En "s/ dev (${ifaces::-1}).+//p" | + # Remove [link-local subnet](https://en.wikipedia.org/wiki/Link-local_address) using grep reverse match (-v) + grep -v "169.254.0.0/16" | + # Sort and remove potential duplicates + sort -u | + # Merge all lines into a single line separated by commas (no trailing ,) + paste -sd, - +) + +if [ -z "${subnets}" ]; then + echo -e "Error, no subnets detected.${clear}" + exit 1 +fi +echo -e "${prev_line}${clear}${subnets}" + +### Configure Consul +printf "${bold}%${spacing}s${clear}: ...\n${red}" "Configure" +code=$(curl -X PUT -H "X-Consul-Token:$CONSUL_TOKEN" --data "${subnets}" -w "%{http_code}" -o /dev/null -s "${url}" || echo $?) +if [ $((code)) -ne 200 ]; then + echo -e "${red}${bold}Failed!${normal} curl returned a status code of '${bold}${code}'${clear}" + exit $((code)) +fi +echo -e "${prev_line}${green}Success${clear}" diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 4af11955..e884e3c3 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -29,18 +29,29 @@ slots: write: [$SNAP_DATA/device-rfid-llrp] apps: + auto-configure: + adapter: none + command-chain: + - snap/command-chain/snapcraft-runner + command: bin/auto-configure.sh + plugs: + - network + - network-observe + - network-control + device-rfid-llrp: adapter: full command: bin/device-rfid-llrp $CONFIG_PRO_ARG $CONF_ARG $REGISTRY_ARG command-chain: - bin/startup-env-var.sh environment: - CONFIG_PRO_ARG: "--cp=consul.http://localhost:8500" + CONFIG_PRO_ARG: "--cp=consul://localhost:8500" CONF_ARG: "--confdir=$SNAP_DATA/config/device-rfid-llrp/res" REGISTRY_ARG: "--registry" DEVICE_PROFILESDIR: "$SNAP_DATA/config/device-rfid-llrp/res/profiles" # DEVICE_DEVICESDIR: "$SNAP_DATA/config/device-rfid-llrp/res/devices" SECRETSTORE_TOKENFILE: $SNAP_DATA/device-rfid-llrp/secrets-token.json + APPCUSTOM_PROVISION_WATCHER_FOLDER: $SNAP_DATA/config/device-rfid-llrp/res/provision_watchers daemon: simple passthrough: install-mode: disable @@ -125,3 +136,10 @@ parts: config-common: plugin: dump source: snap/local/runtime-helpers + + # required for device discovery + static-packages: + source: snap/local + plugin: nil + stage-packages: + - curl \ No newline at end of file