- add create_key function (fails if key already exists) - add setkey_match_prev function (set value if previous value matches) - add missing quotes - add etcd3.plugin --- src/osaf/consensus/plugins/etcd.plugin | 86 +++++++- src/osaf/consensus/plugins/etcd3.plugin | 366 +++++++++++++++++++++++++++++++ src/osaf/consensus/plugins/sample.plugin | 67 +++++- 3 files changed, 501 insertions(+), 18 deletions(-) create mode 100644 src/osaf/consensus/plugins/etcd3.plugin
diff --git a/src/osaf/consensus/plugins/etcd.plugin b/src/osaf/consensus/plugins/etcd.plugin index 586059b32..6ed85ac92 100644 --- a/src/osaf/consensus/plugins/etcd.plugin +++ b/src/osaf/consensus/plugins/etcd.plugin @@ -29,7 +29,7 @@ readonly etcd_timeout="5s" # 0 - success, <value> is echoed to stdout # non-zero - failure get() { - readonly key=$1 + readonly key="$1" if value=$(etcdctl $etcd_options --timeout $etcd_timeout get "$directory$key" 2>&1) then @@ -49,8 +49,8 @@ get() { # 0 - success # non-zero - failure setkey() { - readonly key=$1 - readonly value=$2 + readonly key="$1" + readonly value="$2" if etcdctl $etcd_options --timeout $etcd_timeout set "$directory$key" \ "$value" >/dev/null @@ -61,6 +61,58 @@ setkey() { fi } +# create +# create <key> and set to <value> in key-value store. Fails if the key +# already exists +# params: +# $1 - <key> +# $2 - <value> +# returns: +# 0 - success +# 1 - already exists +# 2 or above - other failure +create_key() { + readonly key="$1" + readonly value="$2" + + if output=$(etcdctl $etcd_options --timeout $etcd_timeout mk "$directory$key" \ + "$value" 2>&1) + then + return 0 + else + if echo $output | grep "already exists" + then + return 1 + fi + fi + + return 2 +} + +# set +# set <key> to <value> in key-value store, if the existing value matches +# <prev> +# params: +# $1 - <key> +# $2 - <value> +# $3 - <prev> +# returns: +# 0 - success +# non-zero - failure +setkey_match_prev() { + readonly key="$1" + readonly value="$2" + readonly prev="$3" + + if etcdctl $etcd_options --timeout $etcd_timeout set "$directory$key" \ + "$value" --swap-with-value "$prev" >/dev/null + then + return 0 + else + return 1 + fi +} + # erase # erase <key> in key-value store # params: @@ -69,7 +121,7 @@ setkey() { # 0 - success # non-zero - failure erase() { - readonly key=$1 + readonly key="$1" if etcdctl $etcd_options --timeout $etcd_timeout \ rm "$directory$key" >/dev/null 2>&1 @@ -90,8 +142,8 @@ erase() { # 2 or above - other failure # NOTE: if lock is already acquired by <owner>, then timeout is extended lock() { - readonly owner=$1 - readonly timeout=$2 + readonly owner="$1" + readonly timeout="$2" if etcdctl $etcd_options --timeout $etcd_timeout \ mk "$directory$keyname" "$owner" \ @@ -145,7 +197,7 @@ lock_owner() { # 2 or above - other failure # unlock() { - readonly owner=$1 + readonly owner="$1" readonly forced=${2:-false} if [ "$forced" = false ]; then @@ -185,7 +237,7 @@ unlock() { # 0 - success, <new_value> is echoed to stdout # non-zero - failure watch() { - readonly key=$1 + readonly key="$1" if value=$(etcdctl $etcd_options --timeout $etcd_timeout \ watch "$directory$key" 2>&1) @@ -216,6 +268,22 @@ case "$1" in setkey "$2" "$3" exit $? ;; + set_if_prev) + if [ "$#" -ne 4 ]; then + echo "Usage: $0 set <key> <value> <previous_value>" + exit 1 + fi + setkey_match_prev "$2" "$3" "$4" + exit $? + ;; + create) + if [ "$#" -ne 3 ]; then + echo "Usage: $0 create <key> <value>" + exit 1 + fi + create_key "$2" "$3" + exit $? + ;; erase) if [ "$#" -ne 2 ]; then echo "Usage: $0 erase <key>" @@ -269,7 +337,7 @@ case "$1" in exit $? ;; *) - echo "Usage: $0 {get|set|erase|lock|unlock|lock_owner|watch|watch_lock}" + echo "Usage: $0 {get|set|create|set_if_prev|erase|lock|unlock|lock_owner|watch|watch_lock}" ;; esac diff --git a/src/osaf/consensus/plugins/etcd3.plugin b/src/osaf/consensus/plugins/etcd3.plugin new file mode 100644 index 000000000..451440567 --- /dev/null +++ b/src/osaf/consensus/plugins/etcd3.plugin @@ -0,0 +1,366 @@ +#!/usr/bin/env bash +# -*- OpenSAF -*- +# +# (C) Copyright 2018 Ericsson AB 2018 - All Rights Reserved. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. This file and program are licensed +# under the GNU Lesser General Public License Version 2.1, February 1999. +# The complete license can be accessed from the following location: +# http://opensource.org/licenses/lgpl-license.php +# See the Copying file included with the OpenSAF distribution for full +# licensing terms. +# +# Please note: this API is subject to change and may be modified +# in a future version of OpenSAF. Future API versions may not be +# backward compatible. This plugin may need to be adapted. + +readonly keyname="opensaf_consensus_lock" +readonly directory="/opensaf/" +readonly etcd_options="" +readonly etcd_timeout="5s" + +export ETCDCTL_API=3 + +# get +# retrieve <value> of <key> from key-value store +# params: +# $1 - <key> +# returns: +# 0 - success, <value> is echoed to stdout +# non-zero - failure +get() { + readonly key="$1" + + if value=$(etcdctl $etcd_options --dial-timeout $etcd_timeout get "$directory$key" | tail -n1) + then + echo "$value" + return 0 + else + return 1 + fi +} + +# set +# set <key> to <value> in key-value store +# params: +# $1 - <key> +# $2 - <value> +# returns: +# 0 - success +# non-zero - failure +setkey() { + readonly key="$1" + readonly value="$2" + + if etcdctl $etcd_options --dial-timeout $etcd_timeout put "$directory$key" \ + "$value" >/dev/null + then + return 0 + else + return 1 + fi +} + +# create +# create <key> and set to <value> in key-value store. Fails if the key +# already exists +# params: +# $1 - <key> +# $2 - <value> +# returns: +# 0 - success +# 1 - already exists +# 2 or above - other failure +create_key() { + readonly key="$1" + readonly value="$2" + # first try to create the key + transaction="create(\""$directory$key"\") = \"0\" + + put \""$directory$key"\" \""$value"\" + + " + output=$(etcdctl $etcd_options --dial-timeout $etcd_timeout txn <<< "$transaction") + if [[ "$output" == *"OK"* ]]; then + return 0 + fi + + if output=$(etcdctl $etcd_options --dial-timeout $etcd_timeout get "$directory$key" | tail -n1) + then + return 1 + else + return 2 + fi +} + +# set +# set <key> to <value> in key-value store, if the existing value matches +# <prev> +# params: +# $1 - <key> +# $2 - <value> +# $3 - <prev> +# returns: +# 0 - success +# non-zero - failure +setkey_match_prev() { + readonly key="$1" + readonly value="$2" + readonly prev="$3" + + # key already exists, make sure it's empty + transaction="value(\""$directory$key"\") = \"$prev\" + + put \""$directory$key"\" \""$value"\" + + " + output=$(etcdctl $etcd_options --dial-timeout $etcd_timeout txn <<< "$transaction") + if [[ "$output" == *"OK"* ]]; then + return 0 + fi + + return 1 +} + +# erase +# erase <key> in key-value store +# params: +# $1 - <key> +# returns: +# 0 - success +# non-zero - failure +erase() { + readonly key="$1" + + if etcdctl $etcd_options --dial-timeout $etcd_timeout \ + del "$directory$key" >/dev/null 2>&1 + then + return 0 + else + return 1 + fi +} + +# lock +# params: +# $1 - <owner>, owner of the lock is set to this +# $2 - <timeout>, will automatically unlock after <timeout> seconds +# returns: +# 0 - success +# 1 - the lock is owned by someone else +# 2 or above - other failure +# NOTE: if lock is already acquired by <owner>, then timeout is extended +# TODO: timeout not yet implemented +lock() { + readonly owner="$1" + readonly timeout="$2" + # first try to create the key + transaction="create(\""$directory$keyname"\") = \"0\" + + put \""$directory$keyname"\" \""$owner"\" + + " + output=$(etcdctl $etcd_options --dial-timeout $etcd_timeout txn <<< "$transaction") + if [[ "$output" == *"OK"* ]]; then + return 0 + fi + + # key already exists, make sure it's empty + transaction="value(\""$directory$keyname"\") = \"\" + + put \""$directory$keyname"\" \""$owner"\" + + " + output=$(etcdctl $etcd_options --dial-timeout $etcd_timeout txn <<< "$transaction") + if [[ "$output" == *"OK"* ]]; then + return 0 + fi + + current_owner=$(etcdctl $etcd_options --dial-timeout $etcd_timeout get "$directory$keyname" | tail -n1) + # see if we already hold the lock + if [ "$current_owner" = "$owner" ]; then + return 0 + fi + + if [ -n "$current_owner" ]; then + # owned by someone else + echo "$current_owner" + return 1 + fi + + # for troubleshooting + echo "$output" + return 2 +} + +# get +# retrieve <owner> of lock +# params: +# none +# returns: +# 0 - success, <owner> is echoed to stdout +# non-zero - failure or not locked +lock_owner() { + get "$keyname" + return $? +} + +# unlock +# params: +# $1 - owner +# $2 - <forced> +# - (optional parameter) +# - if set 'true', will unlock even if lock is not held by node +# - defaults to 'false' +# returns: +# 0 - success +# 1 - the lock is owned by someone else +# 2 or above - other failure +# +unlock() { + readonly owner="$1" + readonly forced=${2:-false} + if [ "$forced" = false ]; then + # unlock on succeeds if owner matches + transaction="value(\""$directory$keyname"\") = \""$owner"\" + + put \""$directory$keyname"\" \"\" + + " + output=$(etcdctl $etcd_options --dial-timeout $etcd_timeout txn <<< "$transaction") + if [[ "$output" == *"OK"* ]]; then + return 0 + fi + + # failed! check we own the lock + current_owner=lock_owner + if [[ "$owner" != "$current_owner" && -n "$current_owner" ]]; then + # for troubleshooting + echo "$output" + echo "$current_owner" + return 1 + fi + + # for troubleshooting + echo "$output" + return 2 + fi + + if etcdctl $etcd_options --dial-timeout $etcd_timeout \ + del "$directory$keyname" >/dev/null 2>&1 + then + return 0 + else + return 2 + fi +} + +# watch +# watch <key> in key-value store +# params: +# $1 - <key> +# returns: +# 0 - success, <new_value> is echoed to stdout +# non-zero - failure +watch() { + readonly watch_key="$1" + etcdctl $etcd_options --dial-timeout $etcd_timeout \ + watch "$directory$watch_key" | grep -m0 \"\" 2>&1 + get "$watch_key" + return 0 +} + +# argument parsing +case "$1" in + get) + if [ "$#" -ne 2 ]; then + echo "Usage: $0 get <key>" + exit 1 + fi + get "$2" + exit $? + ;; + set) + if [ "$#" -ne 3 ]; then + echo "Usage: $0 set <key> <value>" + exit 1 + fi + setkey "$2" "$3" + exit $? + ;; + set_if_prev) + if [ "$#" -ne 4 ]; then + echo "Usage: $0 set <key> <value> <previous_value>" + exit 1 + fi + setkey_match_prev "$2" "$3" "$4" + exit $? + ;; + create) + if [ "$#" -ne 3 ]; then + echo "Usage: $0 create <key> <value>" + exit 1 + fi + create_key "$2" "$3" + exit $? + ;; + erase) + if [ "$#" -ne 2 ]; then + echo "Usage: $0 erase <key>" + exit 1 + fi + erase "$2" + exit $? + ;; + lock) + if [ "$#" -ne 3 ]; then + echo "Usage: $0 lock <owner> <timeout>" + exit 1 + fi + lock "$2" "$3" + exit $? + ;; + lock_owner) + if [ "$#" -ne 1 ]; then + echo "Usage: $0 lock_owner" + exit 1 + fi + lock_owner + exit $? + ;; + unlock) + if [ "$#" -eq 2 ]; then + unlock "$2" + exit $? + elif [ "$#" -eq 3 ] && [ "$3" = "--force" ]; then + unlock "$2" 1 + exit $? + else + echo "Usage: $0 unlock <owner> [--force]" + exit 1 + fi + ;; + watch) + if [ "$#" -ne 2 ]; then + echo "Usage: $0 watch <key>" + exit 1 + fi + watch "$2" + exit $? + ;; + watch_lock) + if [ "$#" -ne 1 ]; then + echo "Usage: $0 watch_lock" + exit 1 + fi + watch "$keyname" + exit $? + ;; + *) + echo "Usage: $0 {get|set|create|set_if_prev|erase|lock|unlock|lock_owner|watch|watch_lock}" + ;; +esac + +exit 1 diff --git a/src/osaf/consensus/plugins/sample.plugin b/src/osaf/consensus/plugins/sample.plugin index 433d23d98..445cf8d84 100644 --- a/src/osaf/consensus/plugins/sample.plugin +++ b/src/osaf/consensus/plugins/sample.plugin @@ -26,7 +26,7 @@ readonly keyname="opensaf_consensus_lock" # 0 - success, <value> is echoed to stdout # non-zero - failure get() { - readonly key=$1 + readonly key="$1" ... } @@ -39,8 +39,41 @@ get() { # 0 - success # non-zero - failure setkey() { - readonly key=$1 - readonly value=$2 + readonly key="$1" + readonly value="$2" + ... +} + +# create +# create <key> and set to <value> in key-value store. Fails if the key +# already exists +# params: +# $1 - <key> +# $2 - <value> +# returns: +# 0 - success +# 1 - already exists +# 2 or above - other failure +create_key() { + readonly key="$1" + readonly value="$2" + ... +} + +# set +# set <key> to <value> in key-value store, if the existing value matches +# <prev> +# params: +# $1 - <key> +# $2 - <value> +# $3 - <prev> +# returns: +# 0 - success +# non-zero - failure +setkey_match_prev() { + readonly key="$1" + readonly value="$2" + readonly prev="$3" ... } @@ -52,7 +85,7 @@ setkey() { # 0 - success # non-zero - failure erase() { - readonly key=$1 + readonly key="$1" ... } @@ -64,8 +97,8 @@ erase() { # 0 - success # non-zero - failure lock() { - readonly owner=$1 - readonly timeout=$2 + readonly owner="$1" + readonly timeout="$2" ... } @@ -92,7 +125,7 @@ lock_owner() { # 1 - the lock is owned by someone else # 2 or above - other failure# unlock() { - readonly owner=$1 + readonly owner="$1" readonly forced=${2:-false} ... } @@ -105,7 +138,7 @@ unlock() { # 0 - success, <new_value> is echoed to stdout # non-zero - failure watch() { - readonly key=$1 + readonly key="$1" .. } @@ -127,6 +160,22 @@ case "$1" in setkey "$2" "$3" exit $? ;; + set_if_prev) + if [ "$#" -ne 4 ]; then + echo "Usage: $0 set <key> <value> <previous_value>" + exit 1 + fi + setkey_match_prev "$2" "$3" "$4" + exit $? + ;; + create) + if [ "$#" -ne 3 ]; then + echo "Usage: $0 create <key> <value>" + exit 1 + fi + create_key "$2" "$3" + exit $? + ;; erase) if [ "$#" -ne 2 ]; then echo "Usage: $0 erase <key>" @@ -180,7 +229,7 @@ case "$1" in exit $? ;; *) - echo "Usage: $0 {get|set|erase|lock|unlock|lock_owner|watch|watch_lock}" + echo "Usage: $0 {get|set|create|set_if_prev|erase|lock|unlock|lock_owner|watch|watch_lock}" ;; esac -- 2.14.1 ------------------------------------------------------------------------------ Check out the vibrant tech community on one of the world's most engaging tech sites, Slashdot.org! http://sdm.link/slashdot _______________________________________________ Opensaf-devel mailing list Opensaf-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/opensaf-devel