---
 src/osaf/consensus/Makefile              |  18 ++
 src/osaf/consensus/keyvalue.cc           | 174 +++++++++++++++++++
 src/osaf/consensus/keyvalue.h            |  57 +++++++
 src/osaf/consensus/plugins/etcd.plugin   | 220 ++++++++++++++++++++++++
 src/osaf/consensus/plugins/sample.plugin | 163 ++++++++++++++++++
 src/osaf/consensus/service.cc            | 277 +++++++++++++++++++++++++++++++
 src/osaf/consensus/service.h             |  75 +++++++++
 7 files changed, 984 insertions(+)
 create mode 100644 src/osaf/consensus/Makefile
 create mode 100644 src/osaf/consensus/keyvalue.cc
 create mode 100644 src/osaf/consensus/keyvalue.h
 create mode 100644 src/osaf/consensus/plugins/etcd.plugin
 create mode 100644 src/osaf/consensus/plugins/sample.plugin
 create mode 100644 src/osaf/consensus/service.cc
 create mode 100644 src/osaf/consensus/service.h

diff --git a/src/osaf/consensus/Makefile b/src/osaf/consensus/Makefile
new file mode 100644
index 000000000..a2c8bc9dd
--- /dev/null
+++ b/src/osaf/consensus/Makefile
@@ -0,0 +1,18 @@
+#      -*- OpenSAF  -*-
+#
+# (C) Copyright 2018 The OpenSAF Foundation
+#
+# 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.
+#
+# Author(s): Ericsson AB
+#
+
+all:
+       $(MAKE) -C ../.. lib/libconsensus.la
diff --git a/src/osaf/consensus/keyvalue.cc b/src/osaf/consensus/keyvalue.cc
new file mode 100644
index 000000000..860a8657f
--- /dev/null
+++ b/src/osaf/consensus/keyvalue.cc
@@ -0,0 +1,174 @@
+/*      -*- OpenSAF  -*-
+ *
+ * (C) Copyright 2018 The OpenSAF Foundation
+ *
+ * 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.
+ *
+ * Author(s): Ericsson AB
+ *
+ */
+#include "osaf/consensus/keyvalue.h"
+#include "base/logtrace.h"
+#include "base/getenv.h"
+#include "base/conf.h"
+
+int KeyValue::Execute(const std::string& command, std::string& output) {
+  TRACE_ENTER();
+  constexpr size_t buf_size = 128;
+  std::array<char, buf_size> buffer;
+  FILE* pipe = popen(command.c_str(), "r");
+  if (pipe == nullptr) {
+    return 1;
+  }
+  output = "";
+  while (feof(pipe) == 0) {
+    if (fgets(buffer.data(), buf_size, pipe) != nullptr) {
+      output += buffer.data();
+    }
+  }
+  const int exit_code = pclose(pipe);
+  if (output.empty() == false && isspace(output.back()) != 0) {
+    // remove newline at end of output
+    output.pop_back();
+  }
+  TRACE("Executed '%s', returning %d", command.c_str(), exit_code);
+  return exit_code;
+}
+
+SaAisErrorT KeyValue::Get(const std::string& key, std::string& value) {
+  TRACE_ENTER();
+
+  const std::string kv_store_cmd = base::GetEnv(
+    "FMS_KEYVALUE_STORE_PLUGIN_CMD", "");
+  const std::string command(kv_store_cmd + " get " + key);
+  int rc = KeyValue::Execute(command, value);
+  TRACE("Read '%s'", value.c_str());
+
+  if (rc == 0) {
+    return SA_AIS_OK;
+  } else {
+    return SA_AIS_ERR_FAILED_OPERATION;
+  }
+}
+
+SaAisErrorT KeyValue::Set(const std::string& key, const std::string& value) {
+  TRACE_ENTER();
+
+  const std::string kv_store_cmd = base::GetEnv(
+    "FMS_KEYVALUE_STORE_PLUGIN_CMD", "");
+  const std::string command(kv_store_cmd + " set " + key + " " + value);
+  std::string output;
+  int rc = KeyValue::Execute(command, output);
+
+  if (rc == 0) {
+    return SA_AIS_OK;
+  } else {
+    return SA_AIS_ERR_FAILED_OPERATION;
+  }
+}
+
+SaAisErrorT KeyValue::Erase(const std::string& key) {
+  TRACE_ENTER();
+
+  const std::string kv_store_cmd = base::GetEnv(
+    "FMS_KEYVALUE_STORE_PLUGIN_CMD", "");
+  const std::string command(kv_store_cmd + " erase " + key);
+  std::string output;
+  int rc = KeyValue::Execute(command, output);
+
+  if (rc == 0) {
+    return SA_AIS_OK;
+  } else {
+    return SA_AIS_ERR_FAILED_OPERATION;
+  }
+}
+
+SaAisErrorT KeyValue::Lock(const std::string& owner,
+                         const unsigned int timeout) {
+  TRACE_ENTER();
+
+  const std::string kv_store_cmd = base::GetEnv(
+    "FMS_KEYVALUE_STORE_PLUGIN_CMD", "");
+  const std::string command(kv_store_cmd + " lock " + owner + " " +
+    std::to_string(timeout));
+  std::string output;
+  int rc = KeyValue::Execute(command, output);
+
+  if (rc == 0) {
+    return SA_AIS_OK;
+  } else {
+    return SA_AIS_ERR_TRY_AGAIN;
+  }
+}
+
+SaAisErrorT KeyValue::Unlock(const std::string& owner) {
+  TRACE_ENTER();
+
+  const std::string kv_store_cmd = base::GetEnv(
+    "FMS_KEYVALUE_STORE_PLUGIN_CMD", "");
+  const std::string command(kv_store_cmd + " unlock " + owner);
+  std::string output;
+  int rc = Execute(command, output);
+
+  if (rc == 0) {
+    return SA_AIS_OK;
+  } else if (rc == 1) {
+    LOG_ER("Lock is owned by another node");
+    return SA_AIS_ERR_INVALID_PARAM;
+  } else {
+    return SA_AIS_ERR_TRY_AGAIN;
+  }
+}
+
+bool KeyValue::IsLockedByThisNode() {
+  TRACE_ENTER();
+
+  const std::string kv_store_cmd = base::GetEnv(
+    "FMS_KEYVALUE_STORE_PLUGIN_CMD", "");
+  const std::string command(kv_store_cmd + " lock_owner");
+  std::string output;
+  int rc = KeyValue::Execute(command, output);
+
+  if (rc == 0) {
+    TRACE("Lock owner is %s", output.c_str());
+    if (output.compare(base::Conf::NodeName()) == 0) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+void threadFunction(const std::string& key,
+  const ConsensusCallback& callback,
+  const uint32_t user_defined) {
+  TRACE_ENTER();
+
+  const std::string kv_store_cmd = base::GetEnv(
+    "FMS_KEYVALUE_STORE_PLUGIN_CMD", "");
+  const std::string command(kv_store_cmd + " watch " + key);
+  std::string value;
+  int rc = KeyValue::Execute(command, value);
+  TRACE("Read '%s'", value.c_str());
+
+  if (rc == 0) {
+    callback(value, user_defined);
+  } else {
+    LOG_ER("Failed to watch %s", key.c_str());
+  }
+}
+
+void KeyValue::Watch(const std::string& key,
+  const ConsensusCallback callback,
+  const uint32_t user_defined) {
+  std::thread t(threadFunction, key, callback, user_defined);
+  t.detach();
+  return;
+}
diff --git a/src/osaf/consensus/keyvalue.h b/src/osaf/consensus/keyvalue.h
new file mode 100644
index 000000000..55d096a61
--- /dev/null
+++ b/src/osaf/consensus/keyvalue.h
@@ -0,0 +1,57 @@
+/*      -*- OpenSAF  -*-
+ *
+ * (C) Copyright 2018 The OpenSAF Foundation
+ *
+ * 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.
+ *
+ * Author(s): Ericsson AB
+ *
+ */
+#ifndef OSAF_CONSENSUS_KEYVALUE_H_
+#define OSAF_CONSENSUS_KEYVALUE_H_
+
+#include <saAis.h>
+#include <string>
+#include <functional>
+#include <thread>
+
+typedef std::function<void(const std::string& new_value,
+  const uint32_t user_defined)> ConsensusCallback;
+
+class KeyValue {
+ public:
+  // Retrieve value of key
+  static SaAisErrorT Get(const std::string& key, std::string& value);
+
+  // Set key to value
+  static SaAisErrorT Set(const std::string& key, const std::string& value);
+
+  // Erase key
+  static SaAisErrorT Erase(const std::string& key);
+
+  // Obtain lock, default timeout is 20 seconds
+  static SaAisErrorT Lock(const std::string& owner,
+    const unsigned int timeout = 20);
+
+  // Release lock
+  static SaAisErrorT Unlock(const std::string& owner);
+
+  // Is locked by this node?
+  static bool IsLockedByThisNode();
+
+  // starts a thread to watch key and call callback if values changes
+  static void Watch(const std::string& key, ConsensusCallback callback,
+    const uint32_t user_defined);
+
+  // internal use
+  static int Execute(const std::string& command, std::string& output);
+};
+
+#endif  // OSAF_CONSENSUS_KEYVALUE_H_
diff --git a/src/osaf/consensus/plugins/etcd.plugin 
b/src/osaf/consensus/plugins/etcd.plugin
new file mode 100644
index 000000000..2ace5c73b
--- /dev/null
+++ b/src/osaf/consensus/plugins/etcd.plugin
@@ -0,0 +1,220 @@
+#!/usr/bin/env bash
+
+readonly keyname="opensaf_consensus_lock"
+readonly etcd_timeout="5s"
+
+# get
+#   retrieve <value> of <key> from key-value store
+# params:
+#   $1 - <key>
+# returns:
+#   0 - success, <value> is echoed to stdout
+#   non-zero - failure
+get() {
+  local -r key=$1
+
+  if value=$(etcdctl --timeout $etcd_timeout get "$key" 2>&1)
+  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
+set() {
+  local -r key=$1
+  local -r value=$2
+
+  if etcdctl --timeout $etcd_timeout set "$key" "$value" 1>& /dev/null
+  then
+    return 0
+  else
+    return 1
+  fi
+}
+
+# erase
+#   erase <key> in key-value store
+# params:
+#   $1 - <key>
+# returns:
+#   0 - success
+#   non-zero - failure
+erase() {
+  local -r key=$1
+
+  if etcdctl --timeout $etcd_timeout rm "$key" 1>& /dev/null
+  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
+#   non-zero - failure
+lock() {
+  local -r owner=$1
+  local -r timeout=$2
+
+  #implementation here
+  if etcdctl --timeout $etcd_timeout mk "$keyname" "$owner" --ttl "$timeout" 
>& /dev/null
+  then
+    return 0
+  else
+    current_owner=$(etcdctl get $keyname)
+    # see if we already hold the lock
+    if [ "$current_owner" == "$owner" ]; then
+      return 0
+    fi
+    return 1
+  fi
+}
+
+# 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() {
+  local -r owner=$1
+  local -r forced=${2:-false}
+
+  if [ "$forced" = false ]; then
+    # check we own the lock
+    current_owner=$(etcdctl --timeout $etcd_timeout get $keyname 2>&1)
+    if [ "$owner" != "$current_owner" ]; then
+      return 1
+    fi
+  fi
+
+  if etcdctl --timeout $etcd_timeout rm $keyname >& /dev/null
+  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() {
+  local -r key=$1
+
+  if value=$(etcdctl --timeout $etcd_timeout watch "$key" 2>&1)
+  then
+    # if the key is removed, then "PrevNode.Value: <value>" is returned
+    echo "$value"
+    return 0
+  else
+    return 1
+  fi
+}
+
+
+# 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
+    set "$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 $?
+    ;;
+  *)
+    echo "Usage: $0 {get|set|erase|lock|unlock|lock_owner|watch}"
+    ;;
+esac
+
+exit 1
diff --git a/src/osaf/consensus/plugins/sample.plugin 
b/src/osaf/consensus/plugins/sample.plugin
new file mode 100644
index 000000000..e0563f72e
--- /dev/null
+++ b/src/osaf/consensus/plugins/sample.plugin
@@ -0,0 +1,163 @@
+#!/usr/bin/env bash
+
+readonly keyname="opensaf_consensus_lock"
+
+# get
+#   retrieve <value> of <key> from key-value store
+# params:
+#   $1 - <key>
+# returns:
+#   0 - success, <value> is echoed to stdout
+#   non-zero - failure
+get() {
+  local -r key=$1
+  ...
+}
+
+# set
+#   set <key> to <value> in key-value store
+# params:
+#   $1 - <key>
+#   $2 - <value>
+# returns:
+#   0 - success
+#   non-zero - failure
+set() {
+  local -r key=$1
+  local -r value=$2
+  ...
+}
+
+# erase
+#   erase <key> in key-value store
+# params:
+#   $1 - <key>
+# returns:
+#   0 - success
+#   non-zero - failure
+erase() {
+  local -r key=$1
+  ...
+}
+
+# lock
+# params:
+#   $1 - <owner>, owner of the lock is set to this
+#   $2 - <timeout>, will automatically unlock after <timeout> seconds
+# returns:
+#   0 - success
+#   non-zero - failure
+lock() {
+  local -r owner=$1
+  local -r timeout=$2
+  ...
+}
+
+# get
+#   retrieve <owner> of lock
+# params:
+#   none
+# returns:
+#   0 - success, <owner> is echoed to stdout
+#   non-zero - failure or not locked
+lock_owner() {
+  ...
+}
+
+# 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() {
+  local -r owner=$1
+  local -r forced=${2:-false}
+  ...
+}
+
+# watch
+#   watch <key> in key-value store
+# params:
+#   $1 - <key>
+# returns:
+#   0 - success, <new_value> is echoed to stdout
+#   non-zero - failure
+watch() {
+  local -r key=$1
+  ..
+}
+
+# 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
+    set "$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 $?
+    ;;
+  *)
+    echo "Usage: $0 {get|set|erase|lock|unlock|lock_owner|watch}"
+    ;;
+esac
+
+exit 1
diff --git a/src/osaf/consensus/service.cc b/src/osaf/consensus/service.cc
new file mode 100644
index 000000000..6131bbe67
--- /dev/null
+++ b/src/osaf/consensus/service.cc
@@ -0,0 +1,277 @@
+/*      -*- OpenSAF  -*-
+ *
+ * (C) Copyright 2018 The OpenSAF Foundation
+ *
+ * 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.
+ *
+ * Author(s): Ericsson AB
+ *
+ */
+#include "osaf/consensus/service.h"
+#include <unistd.h>
+#include <climits>
+#include <thread>
+#include "base/logtrace.h"
+#include "base/conf.h"
+#include "base/getenv.h"
+#include "base/ncssysf_def.h"
+
+SaAisErrorT Consensus::BeginActivePromotion() {
+  TRACE_ENTER();
+  SaAisErrorT rc;
+
+  if (use_consensus_ == false) {
+    return SA_AIS_OK;
+  }
+
+  uint32_t retries = 0;
+  rc = KeyValue::Lock(base::Conf::NodeName(), kLockTimeout);
+  while (rc == SA_AIS_ERR_TRY_AGAIN && retries < kMaxRetry) {
+    TRACE("Waiting for lock");
+    ++retries;
+    std::this_thread::sleep_for(kSleepInterval);
+    rc = KeyValue::Lock(base::Conf::NodeName(), kLockTimeout);
+  }
+
+  LOG_IN("Node %s obtained lock", base::Conf::NodeName().c_str());
+
+  // check current active node
+  std::string current;
+  bool current_valid = false;
+  rc = KeyValue::Get(keyname, current);
+  if (rc == SA_AIS_OK) {
+    current_valid = true;
+    LOG_NO("Current active controller is %s", current.c_str());
+  }
+
+  LOG_NO("Setting active controller to %s", base::Conf::NodeName().c_str());
+  rc = KeyValue::Set(keyname, base::Conf::NodeName());
+
+  if (current_valid == true) {
+    if (current != base::Conf::NodeName()) {
+      // fence the old active controller
+      FenceNode(current);
+    }
+  }
+  return rc;
+}
+
+SaAisErrorT Consensus::EndActivePromotion() {
+  TRACE_ENTER();
+  if (use_consensus_ == false) {
+    return SA_AIS_OK;
+  }
+
+  bool locked = KeyValue::IsLockedByThisNode();
+  if (locked == false) {
+    LOG_ER("Lock unexpectedly released");
+  } else {
+    uint32_t retries = 0;
+    SaAisErrorT rc;
+    rc = KeyValue::Unlock(base::Conf::NodeName());
+    while (rc == SA_AIS_ERR_TRY_AGAIN && retries < kMaxRetry) {
+      LOG_IN("Trying to unlock");
+      ++retries;
+      std::this_thread::sleep_for(kSleepInterval);
+      rc = KeyValue::Unlock(base::Conf::NodeName());
+    }
+    if (rc == SA_AIS_OK) {
+      LOG_IN("Released lock");
+    } else {
+      LOG_ER("Unlock failed");
+    }
+  }
+  return SA_AIS_OK;
+}
+
+SaAisErrorT Consensus::Demote(const std::string& node = "") {
+  TRACE_ENTER();
+  SaAisErrorT rc;
+  if (use_consensus_ == false) {
+    return SA_AIS_OK;
+  }
+
+  uint32_t retries = 0;
+  rc = KeyValue::Lock(base::Conf::NodeName(), kLockTimeout);
+  while (rc == SA_AIS_ERR_TRY_AGAIN && retries < kMaxRetry) {
+    LOG_IN("Waiting for lock");
+    ++retries;
+    std::this_thread::sleep_for(kSleepInterval);
+    rc = KeyValue::Lock(base::Conf::NodeName(), kLockTimeout);
+  }
+
+  // check current active node
+  std::string current;
+  rc = KeyValue::Get(keyname, current);
+  if (rc == SA_AIS_OK) {
+    LOG_NO("Demoting %s as active controller", current.c_str());
+    if (node.empty() == false && node != current) {
+      FenceNode(node);
+    }
+    // if Demote() was called as DemoteCurrentActive() from fmd,
+    // then fmd will fence the node itself
+
+    rc = KeyValue::Erase(keyname);
+    if (rc != SA_AIS_OK) {
+      LOG_ER("Failed to clear active controller in KeyValue");
+    }
+    LOG_NO("Node %s demoted", current.c_str());
+  }
+
+  retries = 0;
+  rc = KeyValue::Unlock(base::Conf::NodeName());
+  while (rc == SA_AIS_ERR_TRY_AGAIN && retries < kMaxRetry) {
+    LOG_IN("Trying to unlock");
+    ++retries;
+    std::this_thread::sleep_for(kSleepInterval);
+    rc = KeyValue::Unlock(base::Conf::NodeName());
+  }
+  if (rc == SA_AIS_OK) {
+    LOG_IN("Released lock");
+  } else {
+    LOG_ER("Unlock failed");
+  }
+
+  return SA_AIS_OK;
+}
+
+SaAisErrorT Consensus::DemoteCurrentActive() {
+  TRACE_ENTER();
+  return Demote();
+}
+
+SaAisErrorT Consensus::DemoteThisNode() {
+  TRACE_ENTER();
+  return Demote(base::Conf::NodeName());
+}
+
+bool Consensus::IsEnabled() const {
+  return use_consensus_;
+}
+
+bool Consensus::IsWritable() const {
+  TRACE_ENTER();
+  if (use_consensus_ == false) {
+    return true;
+  }
+
+  SaAisErrorT rc;
+  rc = KeyValue::Set(test_keyname, base::Conf::NodeName());
+  if (rc == SA_AIS_OK) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+bool Consensus::IsRemoteFencingEnabled() const {
+  return use_remote_fencing_;
+}
+
+std::string Consensus::CurrentActive() const {
+  TRACE_ENTER();
+  SaAisErrorT rc;
+  bool success = false;
+  if (use_consensus_ == false) {
+    return "";
+  }
+
+  uint32_t retries = 0;
+  rc = KeyValue::Lock(base::Conf::NodeName(), kLockTimeout);
+  while (rc == SA_AIS_ERR_TRY_AGAIN && retries < kMaxRetry) {
+    LOG_IN("Waiting for lock");
+    ++retries;
+    std::this_thread::sleep_for(kSleepInterval);
+    rc = KeyValue::Lock(base::Conf::NodeName(), kLockTimeout);
+  }
+
+  // check current active node
+  std::string current;
+  rc = KeyValue::Get(keyname, current);
+  if (rc == SA_AIS_OK) {
+    success = true;
+    LOG_IN("Current active controller is %s", current.c_str());
+  }
+
+  retries = 0;
+  rc = KeyValue::Unlock(base::Conf::NodeName());
+  while (rc == SA_AIS_ERR_TRY_AGAIN && retries < kMaxRetry) {
+    LOG_IN("Trying to unlock");
+    ++retries;
+    std::this_thread::sleep_for(kSleepInterval);
+    rc = KeyValue::Unlock(base::Conf::NodeName());
+  }
+  if (rc == SA_AIS_OK) {
+    LOG_IN("Released lock");
+  } else {
+    LOG_ER("Unlock failed");
+  }
+
+  if (success == true) {
+    return current;
+  } else {
+    return "";
+  }
+}
+
+Consensus::Consensus() {
+  TRACE_ENTER();
+
+  uint32_t split_brain_enable = base::GetEnv("FMS_SPLIT_BRAIN_PREVENTION", 0);
+  std::string kv_store_cmd = base::GetEnv("FMS_KEYVALUE_STORE_PLUGIN_CMD", "");
+  uint32_t use_remote_fencing = base::GetEnv("FMS_USE_REMOTE_FENCING" , 0);
+
+  if (split_brain_enable == 1 && kv_store_cmd.empty() == false) {
+    use_consensus_ = true;
+  } else {
+    use_consensus_ = false;
+  }
+
+  if (use_remote_fencing == 1) {
+    use_remote_fencing_ = true;
+  }
+
+  // needed for base::Conf::NodeName() later
+  base::Conf::InitNodeName();
+
+  if (use_consensus_ == true) {
+    LOG_IN("Split brain prevention is enabled");
+  } else {
+    LOG_IN("Split brain prevention is disabled");
+  }
+}
+
+Consensus::~Consensus() {
+}
+
+bool Consensus::FenceNode(const std::string& node) {
+  if (use_remote_fencing_ == true) {
+    LOG_WA("Fencing remote node %s", node.c_str());
+    // @todo currently passing UINT_MAX as node ID, since
+    // we can't always obtain a valid node ID?
+    opensaf_reboot(UINT_MAX, node.c_str(),
+      "Fencing remote node");
+
+    return true;
+  } else {
+    LOG_WA("Fencing is not enabled. Node %s will not be fenced", node.c_str());
+    return false;
+  }
+}
+
+void Consensus::MonitorActive(ConsensusCallback callback,
+  const uint32_t user_defined) {
+  TRACE_ENTER();
+  if (use_consensus_ == false) {
+    return;
+  }
+
+  KeyValue::Watch(keyname, callback, user_defined);
+}
diff --git a/src/osaf/consensus/service.h b/src/osaf/consensus/service.h
new file mode 100644
index 000000000..ae031cc1f
--- /dev/null
+++ b/src/osaf/consensus/service.h
@@ -0,0 +1,75 @@
+/*      -*- OpenSAF  -*-
+ *
+ * (C) Copyright 2018 The OpenSAF Foundation
+ *
+ * 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.
+ *
+ * Author(s): Ericsson AB
+ *
+ */
+#ifndef OSAF_CONSENSUS_SERVICE_H_
+#define OSAF_CONSENSUS_SERVICE_H_
+
+#include <chrono>
+#include <string>
+#include "saAis.h"
+#include "osaf/consensus/keyvalue.h"
+
+class Consensus {
+ public:
+  // Obtain lock, set active controller to this node
+  SaAisErrorT BeginActivePromotion();
+
+  // Release lock
+  SaAisErrorT EndActivePromotion();
+
+  // Obtain lock, clear current active controller, release lock
+  SaAisErrorT DemoteCurrentActive();
+
+  // Obtain lock, clear this node as active controller, release lock
+  SaAisErrorT DemoteThisNode();
+
+  // Returns active controller as known by the consensus service
+  std::string CurrentActive() const;
+
+  // If the active controller is changed as known by the consensus service,
+  // then callback will be run from a new thread, with <user_defined> returned
+  // in the callback
+  void MonitorActive(ConsensusCallback callback, const uint32_t user_defined);
+
+  // Is consensus service enabled?
+  bool IsEnabled() const;
+
+  // Is the key-value store writable?
+  bool IsWritable() const;
+
+  // Is remote fencing enabled?
+  bool IsRemoteFencingEnabled() const;
+
+  Consensus();
+  virtual ~Consensus();
+
+  Consensus(const Consensus&) = delete;
+  Consensus& operator=(const Consensus&) = delete;
+
+ private:
+  bool use_consensus_ = false;
+  bool use_remote_fencing_ = false;
+  const std::string keyname = "opensaf_active_controller";
+  const std::string test_keyname = "opensaf_write_test";
+  const std::chrono::milliseconds kSleepInterval =
+    std::chrono::milliseconds(100);  // in ms
+  static constexpr uint32_t kLockTimeout = 30;
+  static constexpr uint32_t kMaxRetry = 600;
+  SaAisErrorT Demote(const std::string& node);
+  bool FenceNode(const std::string& node);
+};
+
+#endif  // OSAF_CONSENSUS_SERVICE_H_
-- 
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

Reply via email to