This is an automated email from the ASF dual-hosted git repository.
wangdan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-pegasus.git
The following commit(s) were added to refs/heads/master by this push:
new ea554b23c feat: support encrypted password file during SASL
authentication for ZooKeeper C client (#2293)
ea554b23c is described below
commit ea554b23cfa505f6b43877980c8fe90ddd04287e
Author: Dan Wang <[email protected]>
AuthorDate: Thu Oct 16 16:28:44 2025 +0800
feat: support encrypted password file during SASL authentication for
ZooKeeper C client (#2293)
https://github.com/apache/incubator-pegasus/issues/2292
In addition to supporting plaintext password files, this PR adds support for
**encrypted password files** during SASL authentication. A new configuration
item, `sasl_password_encryption_scheme`, is introduced to specify the
encryption scheme — currently, only **Base64** is supported.
This PR also introduces a new configuration option, `zoo_log_level`, which
allows setting the **log level** for the ZooKeeper C client to help diagnose
ZooKeeper-related issues.
Finally, SASL authentication tests for the ZooKeeper C client — which were
previously missing — have been added to ensure proper functionality.
Following configurations are newly added:
```diff
[zookeeper]
+ zoo_log_level = "ZOO_LOG_LEVEL_INFO"
+ sasl_password_encryption_scheme = "base64"
```
---
.github/workflows/lint_and_test_cpp.yaml | 9 +-
admin_tools/start_zk.sh | 24 +-
run.sh | 15 +-
src/zookeeper/test/CMakeLists.txt | 2 +
src/zookeeper/test/config-test.ini | 1 -
src/zookeeper/test/sasl_auth/CMakeLists.txt | 51 ++++
src/zookeeper/test/sasl_auth/config-test.ini | 80 ++++++
src/zookeeper/test/sasl_auth/main.cpp | 67 +++++
src/zookeeper/test/sasl_auth/run.sh | 39 +++
.../sasl_auth/zookeeper_session_sasl_auth_test.cpp | 125 +++++++++
.../test/zookeeper_session_no_auth_test.cpp | 35 +++
src/zookeeper/test/zookeeper_session_test.h | 178 +++++++++++++
src/zookeeper/test/zookeeper_session_test_base.cpp | 281 +++++++++++++++++++++
src/zookeeper/test/zookeeper_session_test_base.h | 158 ++++++++++++
src/zookeeper/zookeeper_session.cpp | 203 ++++++++++-----
src/zookeeper/zookeeper_session.h | 27 +-
16 files changed, 1219 insertions(+), 76 deletions(-)
diff --git a/.github/workflows/lint_and_test_cpp.yaml
b/.github/workflows/lint_and_test_cpp.yaml
index 97a25634a..be73a099d 100644
--- a/.github/workflows/lint_and_test_cpp.yaml
+++ b/.github/workflows/lint_and_test_cpp.yaml
@@ -173,7 +173,7 @@ jobs:
- dsn.zookeeper.tests
# TODO(yingchun): Disable it because we find it's too flaky, we will
re-enable it after
# it has been optimized.
-# - partition_split_test
+ # - partition_split_test
- pegasus_geo_test
- pegasus_rproxy_test
- pegasus_unit_test
@@ -181,6 +181,7 @@ jobs:
- restore_test
- security_test
- throttle_test
+ - zookeeper_sasl_auth_test
needs: build_Release
runs-on: ubuntu-latest
env:
@@ -259,7 +260,7 @@ jobs:
- dsn.zookeeper.tests
# TODO(yingchun): Disable it because we find it's too flaky, we will
re-enable it after
# it has been optimized.
-# - partition_split_test
+ # - partition_split_test
- pegasus_geo_test
- pegasus_rproxy_test
- pegasus_unit_test
@@ -268,7 +269,8 @@ jobs:
- security_test
# TODO(yingchun): Disable it because we find it's too flaky, we will
re-enable it after
# it has been optimized.
-# - throttle_test
+ # - throttle_test
+ - zookeeper_sasl_auth_test
needs: build_ASAN
runs-on: ubuntu-latest
env:
@@ -356,6 +358,7 @@ jobs:
# - restore_test
# - security_test
# - throttle_test
+# - zookeeper_sasl_auth_test
# needs: build_UBSAN
# runs-on: ubuntu-latest
# env:
diff --git a/admin_tools/start_zk.sh b/admin_tools/start_zk.sh
index 2bc37356b..03b7f9f00 100755
--- a/admin_tools/start_zk.sh
+++ b/admin_tools/start_zk.sh
@@ -23,6 +23,7 @@ CWD=$(cd $(dirname $0) && pwd)
# Options:
# INSTALL_DIR <dir>
# PORT <port>
+# SASL_AUTH YES|NO
if [ -z "$INSTALL_DIR" ]
then
@@ -36,6 +37,11 @@ then
exit 1
fi
+if [ -z "$SASL_AUTH" ]
+then
+ SASL_AUTH=NO
+fi
+
if ! mkdir -p "$INSTALL_DIR";
then
echo "ERROR: mkdir $INSTALL_DIR failed"
@@ -54,7 +60,23 @@ fi
ZOOKEEPER_PORT=$PORT
-cp $ZOOKEEPER_HOME/conf/zoo_sample.cfg $ZOOKEEPER_HOME/conf/zoo.cfg
+if [ "$SASL_AUTH" == "YES" ]; then
+ {
+ echo "Server {"
+ echo " org.apache.zookeeper.server.auth.DigestLoginModule required"
+ echo " user_myuser=\"mypassword\";"
+ echo "};"
+ } > $ZOOKEEPER_HOME/conf/jaas.conf
+
+ PROPERTIES=(
+ "-Dzookeeper.sessionRequireClientSASLAuth=true"
+
"-Dzookeeper.authProvider.1=org.apache.zookeeper.server.auth.SASLAuthenticationProvider"
+ "-Djava.security.auth.login.config=$ZOOKEEPER_HOME/conf/jaas.conf"
+ )
+ export JVMFLAGS="${JVMFLAGS} ${PROPERTIES[@]}"
+fi
+
+cp -a $ZOOKEEPER_HOME/conf/zoo_sample.cfg $ZOOKEEPER_HOME/conf/zoo.cfg
sed -i "s@dataDir=/tmp/zookeeper@dataDir=$ZOOKEEPER_HOME/data@"
$ZOOKEEPER_HOME/conf/zoo.cfg
sed -i "s@clientPort=2181@clientPort=$ZOOKEEPER_PORT@"
$ZOOKEEPER_HOME/conf/zoo.cfg
echo "admin.enableServer=false" >> $ZOOKEEPER_HOME/conf/zoo.cfg
diff --git a/run.sh b/run.sh
index c8fc079f5..04ab68ea2 100755
--- a/run.sh
+++ b/run.sh
@@ -441,6 +441,7 @@ function run_test()
recovery_test
restore_test
throttle_test
+ zookeeper_sasl_auth_test
)
local onebox_opts=""
local test_opts=""
@@ -538,7 +539,12 @@ function run_test()
else
# Restart ZK in what ever case.
run_stop_zk
- run_start_zk
+
+ if [ "${module}" == "zookeeper_sasl_auth_test" ]; then
+ run_start_zk --sasl_auth
+ else
+ run_start_zk
+ fi
fi
# Run server test.
@@ -605,6 +611,7 @@ function usage_start_zk()
echo " zookeeper install directory,"
echo " if not set, then default is './.zk_install'"
echo " -p|--port <port> listen port of zookeeper, default is 22181"
+ echo " -s|--sasl_auth start zookeeper with SASL Auth, default no"
}
function run_start_zk()
@@ -618,6 +625,7 @@ function run_start_zk()
INSTALL_DIR=`pwd`/.zk_install
PORT=22181
+ SASL_AUTH=NO
while [[ $# > 0 ]]; do
key="$1"
case $key in
@@ -633,6 +641,9 @@ function run_start_zk()
PORT=$2
shift
;;
+ -s|--sasl_auth)
+ SASL_AUTH=YES
+ ;;
*)
echo "ERROR: unknown option \"$key\""
echo
@@ -660,7 +671,7 @@ function run_start_zk()
fi
fi
- INSTALL_DIR="$INSTALL_DIR" PORT="$PORT" $ROOT/admin_tools/start_zk.sh
+ INSTALL_DIR="$INSTALL_DIR" PORT="$PORT" SASL_AUTH="$SASL_AUTH"
$ROOT/admin_tools/start_zk.sh
}
#####################
diff --git a/src/zookeeper/test/CMakeLists.txt
b/src/zookeeper/test/CMakeLists.txt
index 7dabd1faf..63bf23444 100644
--- a/src/zookeeper/test/CMakeLists.txt
+++ b/src/zookeeper/test/CMakeLists.txt
@@ -54,3 +54,5 @@ set(MY_BINPLACES
)
dsn_add_test()
+
+add_subdirectory(sasl_auth)
diff --git a/src/zookeeper/test/config-test.ini
b/src/zookeeper/test/config-test.ini
index 5a07160ae..bceaaf999 100644
--- a/src/zookeeper/test/config-test.ini
+++ b/src/zookeeper/test/config-test.ini
@@ -65,7 +65,6 @@ pause_on_start = false
logging_start_level = LOG_LEVEL_DEBUG
logging_factory_name = dsn::tools::simple_logger
-
[tools.simple_logger]
fast_flush = true
short_header = false
diff --git a/src/zookeeper/test/sasl_auth/CMakeLists.txt
b/src/zookeeper/test/sasl_auth/CMakeLists.txt
new file mode 100644
index 000000000..add58ccda
--- /dev/null
+++ b/src/zookeeper/test/sasl_auth/CMakeLists.txt
@@ -0,0 +1,51 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+
+set(MY_PROJ_NAME zookeeper_sasl_auth_test)
+project(${MY_PROJ_NAME} C CXX)
+
+include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../")
+
+# Source files under CURRENT project directory will be automatically included.
+# You can manually set MY_PROJ_SRC to include source files under other
directories.
+set(MY_PROJ_SRC
+ "${CMAKE_CURRENT_SOURCE_DIR}/../zookeeper_session_test_base.cpp")
+
+# Search mode for source files under CURRENT project directory?
+# "GLOB_RECURSE" for recursive search
+# "GLOB" for non-recursive search
+set(MY_SRC_SEARCH_MODE "GLOB")
+
+set(MY_PROJ_LIBS
+ dsn.replication.zookeeper_provider
+ dsn_runtime
+ zookeeper
+ hashtable
+ gtest
+ sasl2
+ rocksdb
+ lz4
+ zstd
+ snappy)
+
+set(MY_BOOST_LIBS Boost::system Boost::filesystem)
+
+set(MY_BINPLACES
+ "${CMAKE_CURRENT_SOURCE_DIR}/config-test.ini"
+ "${CMAKE_CURRENT_SOURCE_DIR}/run.sh")
+
+dsn_add_test()
diff --git a/src/zookeeper/test/sasl_auth/config-test.ini
b/src/zookeeper/test/sasl_auth/config-test.ini
new file mode 100644
index 000000000..fbafa4c06
--- /dev/null
+++ b/src/zookeeper/test/sasl_auth/config-test.ini
@@ -0,0 +1,80 @@
+; Licensed to the Apache Software Foundation (ASF) under one
+; or more contributor license agreements. See the NOTICE file
+; distributed with this work for additional information
+; regarding copyright ownership. The ASF licenses this file
+; to you 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.
+
+[apps..default]
+run = true
+count = 1
+;network.client.RPC_CHANNEL_TCP = dsn::tools::sim_network_provider, 65536
+;network.client.RPC_CHANNEL_UDP = dsn::tools::sim_network_provider, 65536
+;network.server.0.RPC_CHANNEL_TCP = dsn::tools::sim_network_provider, 65536
+
+[apps.client]
+type = test
+arguments =
+run = true
+ports =
+count = 1
+delay_seconds = 1
+pools = THREAD_POOL_DEFAULT
+
+[core]
+;tool = simulator
+tool = nativerun
+
+;toollets = tracer, profiler
+;fault_injector
+pause_on_start = false
+
+logging_start_level = LOG_LEVEL_DEBUG
+logging_factory_name = dsn::tools::simple_logger
+
+[tools.simple_logger]
+fast_flush = true
+short_header = false
+stderr_start_level = LOG_LEVEL_WARNING
+
+[tools.simulator]
+random_seed = 0
+
+[network]
+; how many network threads for network library (used by asio)
+io_service_worker_count = 2
+
+[task..default]
+is_trace = true
+is_profile = true
+allow_inline = false
+rpc_call_channel = RPC_CHANNEL_TCP
+rpc_message_header_format = dsn
+rpc_timeout_milliseconds = 5000
+
+[task.LPC_RPC_TIMEOUT]
+is_trace = false
+is_profile = false
+
+; specification for each thread pool
+[threadpool..default]
+worker_count = 2
+
+[threadpool.THREAD_POOL_DEFAULT]
+partitioned = false
+worker_priority = THREAD_xPRIORITY_NORMAL
+
+[zookeeper]
+hosts_list = localhost:22181
+timeout_ms = 30000
+zoo_log_level = ZOO_LOG_LEVEL_DEBUG
diff --git a/src/zookeeper/test/sasl_auth/main.cpp
b/src/zookeeper/test/sasl_auth/main.cpp
new file mode 100644
index 000000000..d163b962c
--- /dev/null
+++ b/src/zookeeper/test/sasl_auth/main.cpp
@@ -0,0 +1,67 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+
+#include <gtest/gtest.h>
+#include <atomic>
+#include <string>
+#include <vector>
+
+#include "runtime/app_model.h"
+#include "runtime/service_app.h"
+#include "utils/error_code.h"
+#include "utils/synchronize.h"
+
+dsn::utils::notify_event g_on_completed;
+std::atomic_int g_test_result{0};
+
+// Mock a service_app which is required by ZookeeperSessionConnector.
+class test_client : public dsn::service_app
+{
+public:
+ explicit test_client(const dsn::service_app_info *info) :
dsn::service_app(info) {}
+
+ dsn::error_code start(const std::vector<std::string> &args) override
+ {
+ g_test_result = RUN_ALL_TESTS();
+
+ // All tests have been finished.
+ g_on_completed.notify();
+
+ return ::dsn::ERR_OK;
+ }
+
+ dsn::error_code stop(bool cleanup = false) override { return dsn::ERR_OK;
} // NOLINT
+};
+
+GTEST_API_ int main(int argc, char **argv)
+{
+ testing::InitGoogleTest(&argc, argv);
+
+ // Register all services.
+ dsn::service_app::register_factory<test_client>("test");
+
+ dsn_run_config("config-test.ini", false);
+
+ // Wait until all tests are finished.
+ g_on_completed.wait();
+
+#ifndef ENABLE_GCOV
+ dsn_exit(g_test_result);
+#endif
+
+ return g_test_result;
+}
diff --git a/src/zookeeper/test/sasl_auth/run.sh
b/src/zookeeper/test/sasl_auth/run.sh
new file mode 100755
index 000000000..50f64f818
--- /dev/null
+++ b/src/zookeeper/test/sasl_auth/run.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+
+if [ -z "${REPORT_DIR}" ]; then
+ REPORT_DIR="."
+fi
+
+output_xml="${REPORT_DIR}/zookeeper_sasl_auth_test.xml"
+GTEST_OUTPUT="xml:${output_xml}" ./zookeeper_sasl_auth_test
+
+if [ $? -ne 0 ]; then
+ echo "run zookeeper_sasl_auth_test failed"
+ echo "---- ls ----"
+ ls -l
+ if [ `find . -name pegasus.log.* | wc -l` -ne 0 ]; then
+ echo "---- tail -n 100 pegasus.log.* ----"
+ tail -n 100 `find . -name pegasus.log.*`
+ fi
+ if [ -f core ]; then
+ echo "---- gdb ./zookeeper_sasl_auth_test core ----"
+ gdb ./zookeeper_sasl_auth_test core -ex "thread apply all bt" -ex "set
pagination 0" -batch
+ fi
+ exit 1
+fi
diff --git a/src/zookeeper/test/sasl_auth/zookeeper_session_sasl_auth_test.cpp
b/src/zookeeper/test/sasl_auth/zookeeper_session_sasl_auth_test.cpp
new file mode 100644
index 000000000..f2a7e0d16
--- /dev/null
+++ b/src/zookeeper/test/sasl_auth/zookeeper_session_sasl_auth_test.cpp
@@ -0,0 +1,125 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+
+#include <zookeeper/zookeeper.h>
+#include <cstdio>
+#include <string>
+
+#include "gtest/gtest.h"
+#include "utils/flags.h"
+#include "zookeeper/test/zookeeper_session_test_base.h"
+#include "zookeeper_session_test.h"
+
+DSN_DECLARE_string(sasl_mechanisms_type);
+DSN_DECLARE_string(sasl_user_name);
+DSN_DECLARE_string(sasl_password_file);
+DSN_DECLARE_string(sasl_password_encryption_scheme);
+
+namespace dsn::dist {
+
+namespace {
+
+// The correct password plaintext and its encrypted strings.
+const std::string kPlaintextPassword("mypassword");
+const std::string kBase64Password("bXlwYXNzd29yZA==");
+
+// Write the given `password` into the file. The `password` can be either
unencrypted
+// or encrypted.
+void write_password(const std::string &password)
+{
+ FILE *f = fopen("sasl_auth.password", "w");
+ ASSERT_NE(nullptr, f);
+ ASSERT_EQ(password.size(), fwrite(password.c_str(), 1, password.size(),
f));
+ ASSERT_EQ(0, fclose(f));
+}
+
+// Configure SASL auth with specified `encryption_scheme`.
+void config_sasl(const char *encryption_scheme)
+{
+ FLAGS_sasl_mechanisms_type = "DIGEST-MD5";
+ FLAGS_sasl_user_name = "myuser";
+ FLAGS_sasl_password_file = "sasl_auth.password";
+ FLAGS_sasl_password_encryption_scheme = encryption_scheme;
+}
+
+} // anonymous namespace
+
+class ZookeeperSessionSASLConnectTest : public ZookeeperSessionConnector
+{
+};
+
+TEST_F(ZookeeperSessionSASLConnectTest, CorrectPlaintextPasswordByEmptyScheme)
+{
+ write_password(kPlaintextPassword);
+ config_sasl("");
+ test_connect(ZOO_CONNECTED_STATE);
+}
+
+TEST_F(ZookeeperSessionSASLConnectTest,
CorrectPlaintextPasswordBySpecifiedScheme)
+{
+ write_password(kPlaintextPassword);
+ config_sasl("plaintext");
+ test_connect(ZOO_CONNECTED_STATE);
+}
+
+TEST_F(ZookeeperSessionSASLConnectTest, CorrectBase64Password)
+{
+ write_password(kBase64Password);
+ config_sasl("base64");
+ test_connect(ZOO_CONNECTED_STATE);
+}
+
+TEST_F(ZookeeperSessionSASLConnectTest, WrongPlaintextPasswordByEmptyScheme)
+{
+ write_password("password");
+ config_sasl("");
+ test_connect(ZOO_AUTH_FAILED_STATE);
+}
+
+TEST_F(ZookeeperSessionSASLConnectTest,
WrongPlaintextPasswordBySpecifiedScheme)
+{
+ write_password("password");
+ config_sasl("plaintext");
+ test_connect(ZOO_AUTH_FAILED_STATE);
+}
+
+TEST_F(ZookeeperSessionSASLConnectTest, WrongBase64Password)
+{
+ write_password("cGFzc3dvcmQ="); // base64 encoding for "password".
+ config_sasl("base64");
+ test_connect(ZOO_AUTH_FAILED_STATE);
+}
+
+class ZookeeperSessionSASLAuthTest : public ZookeeperSessionTestBase
+{
+protected:
+ ZookeeperSessionSASLAuthTest();
+};
+
+ZookeeperSessionSASLAuthTest::ZookeeperSessionSASLAuthTest()
+{
+ write_password(kBase64Password);
+ config_sasl("base64");
+ // test_connect() will be called in ZookeeperSessionTestBase::SetUp().
+}
+
+using ZookeeperSessionSASLAuthTestImpl =
::testing::Types<ZookeeperSessionSASLAuthTest>;
+INSTANTIATE_TYPED_TEST_SUITE_P(ZookeeperSASLAuthTest,
+ ZookeeperSessionTest,
+ ZookeeperSessionSASLAuthTestImpl);
+
+} // namespace dsn::dist
diff --git a/src/zookeeper/test/zookeeper_session_no_auth_test.cpp
b/src/zookeeper/test/zookeeper_session_no_auth_test.cpp
new file mode 100644
index 000000000..6d527ee97
--- /dev/null
+++ b/src/zookeeper/test/zookeeper_session_no_auth_test.cpp
@@ -0,0 +1,35 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+
+#include <memory>
+
+#include "gtest/gtest.h"
+#include "zookeeper_session_test.h"
+#include "zookeeper_session_test_base.h"
+
+namespace dsn::dist {
+
+class ZookeeperSessionNoAuthTest : public ZookeeperSessionTestBase
+{
+};
+
+using ZookeeperSessionNoAuthTestImpl =
::testing::Types<ZookeeperSessionNoAuthTest>;
+INSTANTIATE_TYPED_TEST_SUITE_P(ZookeeperNoAuthTest,
+ ZookeeperSessionTest,
+ ZookeeperSessionNoAuthTestImpl);
+
+} // namespace dsn::dist
diff --git a/src/zookeeper/test/zookeeper_session_test.h
b/src/zookeeper/test/zookeeper_session_test.h
new file mode 100644
index 000000000..7091098de
--- /dev/null
+++ b/src/zookeeper/test/zookeeper_session_test.h
@@ -0,0 +1,178 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+
+#include <fmt/core.h>
+#include <gtest/gtest.h>
+#include <zookeeper/zookeeper.h>
+#include <atomic>
+#include <cstddef>
+#include <iterator>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "runtime/service_app.h"
+#include "utils/ports.h"
+
+namespace dsn::dist {
+
+// Test fixure that provides cases for zookeeper_session. The initialization
processes
+// of the BaseFixtures it derives from may vary, which is also the reason why
we use
+// a fixture class template and define a type-parameterized test suite.
+//
+// Any test that depends on the ZooKeeper server that starts with SASL auth
should be put
+// into `zookeeper_sasl_auth_test`, which will start ZooKeeper server with
configurations
+// that enable SASL auth.
+template <typename BaseFixture>
+class ZookeeperSessionTest : public BaseFixture
+{
+public:
+ ~ZookeeperSessionTest() override = default;
+
+protected:
+ ZookeeperSessionTest() = default;
+
+ void test_node_operations(const std::string &path,
+ const std::string &sub_path,
+ const std::string &data)
+ {
+ // The "this" pointer should be kept here since it is required to
delay name lookup
+ // while accessing members of the base class that depends on the
template parameters.
+
+ // Delete the node if any in case previous tests failed.
+ this->test_delete_node(path);
+
+ // Ensure currently the node does not exist.
+ this->test_no_node(path);
+
+ // Updating the node will fail since it has not been created.
+ this->test_set_data(path, data, ZNONODE);
+
+ // The node has not been created, thus its sub nodes cannot be created.
+ this->test_create_node(sub_path, data, ZNONODE);
+
+ // Create the node with some data.
+ this->test_create_node(path, data, ZOK);
+ this->test_has_data(path, data);
+
+ // Creating the node repeatedly will fail.
+ this->test_create_node(path, data, ZNODEEXISTS);
+
+ // Updating the node with another data will succeed since it has been
existing.
+ const auto another_data = fmt::format("another_{}", data);
+ this->test_set_data(path, another_data, ZOK);
+ this->test_has_data(path, another_data);
+
+ // Delete the node.
+ this->test_delete_node(path, ZOK);
+ this->test_no_node(path);
+
+ // Deleting the node repeatedly will fail.
+ this->test_delete_node(path, ZNONODE);
+ }
+
+ void delete_nodes(const std::string &path, const std::vector<std::string>
&sub_nodes)
+ {
+ for (const auto &sub_node : sub_nodes) {
+ this->test_delete_node(fmt::format("{}/{}", path, sub_node));
+ }
+ this->test_delete_node(path);
+ }
+
+ void test_sub_nodes(const std::string &path,
+ const std::string &new_sub_node,
+ std::vector<std::string> &&expected_sub_nodes)
+ {
+ const auto new_sub_path = fmt::format("{}/{}", path, new_sub_node);
+
+ // Create the new sub node.
+ this->test_create_node(new_sub_path, new_sub_node, ZOK);
+ this->test_has_data(new_sub_path, new_sub_node);
+
+ // Check whether all fetched sub nodes are expected.
+ this->test_get_sub_nodes(path, ZOK, std::move(expected_sub_nodes));
+ }
+
+private:
+ DISALLOW_COPY_AND_ASSIGN(ZookeeperSessionTest);
+ DISALLOW_MOVE_AND_ASSIGN(ZookeeperSessionTest);
+};
+
+TYPED_TEST_SUITE_P(ZookeeperSessionTest);
+
+TYPED_TEST_P(ZookeeperSessionTest, OperateNode)
+{
+ // The node with single-level path.
+ static const std::string kPath("/ZookeeperSessionTest");
+
+ // The sub node with two-level path.
+ static const std::string kSubPath(fmt::format("{}/OperateNode", kPath));
+
+ // The data of the node.
+ static const std::string kData("hello");
+
+ // Test the node whose path is single-level.
+ this->test_node_operations(kPath, kSubPath, kData);
+
+ // Create the node again since next we will test its sub node.
+ this->test_create_node(kPath, kData, ZOK);
+ this->test_has_data(kPath, kData);
+
+ // Test the sub node whose path is two-level.
+ this->test_node_operations(kSubPath, fmt::format("{}/ThirdLevelNode",
kSubPath), "world");
+}
+
+TYPED_TEST_P(ZookeeperSessionTest, GetSubNodes)
+{
+ // The node with single-level path.
+ static const std::string kPath("/ZookeeperSessionTest");
+
+ // The data of the node.
+ static const std::string kData("hello");
+
+ // The sub nodes.
+ static const std::vector<std::string> kSubNodes{
+ "SubNode0",
+ "SubNode1",
+ "SubNode2",
+ };
+
+ // Delete all of the nodes if any in case previous tests failed.
+ this->delete_nodes(kPath, kSubNodes);
+ this->test_no_node(kPath);
+
+ // Create the node.
+ this->test_create_node(kPath, kData, ZOK);
+ this->test_has_data(kPath, kData);
+
+ for (size_t i = 0; i < kSubNodes.size(); ++i) {
+ // Create the sub node one by one.
+ this->test_sub_nodes(kPath,
+ kSubNodes[i],
+ std::vector<std::string>(
+ kSubNodes.begin(),
+ std::next(kSubNodes.begin(),
static_cast<std::ptrdiff_t>(i + 1))));
+ }
+
+ // Clear all of the nodes.
+ this->delete_nodes(kPath, kSubNodes);
+ this->test_no_node(kPath);
+}
+
+REGISTER_TYPED_TEST_SUITE_P(ZookeeperSessionTest, OperateNode, GetSubNodes);
+
+} // namespace dsn::dist
diff --git a/src/zookeeper/test/zookeeper_session_test_base.cpp
b/src/zookeeper/test/zookeeper_session_test_base.cpp
new file mode 100644
index 000000000..1d331691e
--- /dev/null
+++ b/src/zookeeper/test/zookeeper_session_test_base.cpp
@@ -0,0 +1,281 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+
+#include <zookeeper/zookeeper.h>
+#include <zookeeper/zookeeper.jute.h>
+#include <algorithm>
+#include <atomic>
+#include <iostream>
+#include <iterator>
+#include <utility>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "runtime/service_app.h"
+#include "utils/blob.h"
+#include "utils/defer.h"
+#include "utils/flags.h"
+#include "utils/synchronize.h"
+#include "zookeeper_session_test_base.h"
+
+DSN_DECLARE_int32(timeout_ms);
+
+namespace dsn::dist {
+
+void ZookeeperSessionConnector::TearDown() { _session->detach(this); }
+
+void ZookeeperSessionConnector::test_connect(int expected_zoo_state)
+{
+ _session =
std::make_unique<zookeeper_session>(service_app::current_service_app_info());
+
+ std::atomic_int actual_zoo_state{0};
+ std::atomic_bool first_call{true};
+ utils::notify_event on_attached;
+
+ const int zoo_state = _session->attach(
+ this,
+ [expected_zoo_state, &actual_zoo_state, &first_call, &on_attached](int
zoo_state) mutable {
+ std::cout << "[on_zoo_session_event] zoo_state = " << zoo_state <<
std::endl;
+
+ actual_zoo_state = zoo_state;
+
+ if (first_call && zoo_state == expected_zoo_state) {
+ first_call = false;
+ on_attached.notify();
+ return;
+ }
+ });
+
+ ASSERT_TRUE(_session);
+
+ if (zoo_state != expected_zoo_state) {
+ on_attached.wait_for(FLAGS_timeout_ms);
+
+ // Callback for attach() may not be called while the state is changed.
+ if (actual_zoo_state != 0) {
+ ASSERT_EQ(expected_zoo_state, actual_zoo_state);
+ }
+
+ ASSERT_EQ(expected_zoo_state, _session->session_state());
+ }
+}
+
+void ZookeeperSessionTestBase::SetUp() { test_connect(ZOO_CONNECTED_STATE); }
+
+void ZookeeperSessionTestBase::TearDown() {
ZookeeperSessionConnector::TearDown(); }
+
+void ZookeeperSessionTestBase::operate_node(
+ const std::string &path,
+ zookeeper_session::ZOO_OPERATION op_type,
+ const std::string &data,
+ std::function<void(zookeeper_session::zoo_opcontext *)> &&callback,
+ int &zerr)
+{
+ utils::notify_event on_completed;
+
+ auto *op = zookeeper_session::create_context();
+ op->_optype = op_type;
+ op->_input._path = path;
+ op->_input._value = blob::create_from_bytes(std::string(data));
+ op->_callback_function =
+ [&zerr, &callback, &on_completed](zookeeper_session::zoo_opcontext
*op) mutable {
+ zerr = op->_output.error;
+
+ if (callback) {
+ callback(op);
+ }
+
+ on_completed.notify();
+ };
+
+ _session->visit(op);
+ on_completed.wait();
+}
+
+void ZookeeperSessionTestBase::operate_node(const std::string &path,
+ zookeeper_session::ZOO_OPERATION
op_type,
+ int &zerr)
+{
+ operate_node(path, op_type, "", nullptr, zerr);
+}
+
+void ZookeeperSessionTestBase::test_operate_node(
+ const std::string &path,
+ zookeeper_session::ZOO_OPERATION op_type,
+ const std::string &data,
+ std::function<void(zookeeper_session::zoo_opcontext *)> &&callback,
+ int expected_zerr)
+{
+ int actual_zerr{0};
+ operate_node(path, op_type, data, std::move(callback), actual_zerr);
+
+ ASSERT_EQ(expected_zerr, actual_zerr)
+ << "expected_zerr = \"" << zerror(expected_zerr) << "\", actual_zerr =
\""
+ << zerror(actual_zerr) << "\", path = " << path << ", op_type = " <<
op_type
+ << ", data = \"" << data << "\"";
+}
+
+void ZookeeperSessionTestBase::test_operate_node(const std::string &path,
+
zookeeper_session::ZOO_OPERATION op_type,
+ const std::string &data,
+ int expected_zerr)
+{
+ test_operate_node(path, op_type, data, nullptr, expected_zerr);
+}
+
+void ZookeeperSessionTestBase::test_operate_node(
+ const std::string &path,
+ zookeeper_session::ZOO_OPERATION op_type,
+ std::function<void(zookeeper_session::zoo_opcontext *)> &&callback,
+ int expected_zerr)
+{
+ test_operate_node(path, op_type, "", std::move(callback), expected_zerr);
+}
+
+void ZookeeperSessionTestBase::test_operate_node(const std::string &path,
+
zookeeper_session::ZOO_OPERATION op_type,
+ int expected_zerr)
+{
+ test_operate_node(path, op_type, "", expected_zerr);
+}
+
+void ZookeeperSessionTestBase::test_create_node(const std::string &path,
+ const std::string &data,
+ int expected_zerr)
+{
+ test_operate_node(path, zookeeper_session::ZOO_CREATE, data,
expected_zerr);
+}
+
+void ZookeeperSessionTestBase::test_delete_node(const std::string &path, int
expected_zerr)
+{
+ test_operate_node(path, zookeeper_session::ZOO_DELETE, expected_zerr);
+}
+
+void ZookeeperSessionTestBase::test_delete_node(const std::string &path)
+{
+ using ::testing::AnyOf;
+ using ::testing::Eq;
+
+ int actual_zerr{0};
+ operate_node(path, zookeeper_session::ZOO_DELETE, actual_zerr);
+ ASSERT_THAT(actual_zerr, AnyOf(Eq(ZOK), Eq(ZNONODE)))
+ << "expected_zerr = \"" << zerror(ZOK) << "\" or \"" << zerror(ZNONODE)
+ << "\", actual_zerr = \"" << zerror(actual_zerr) << "\", path = " <<
path
+ << ", op_type = " << zookeeper_session::ZOO_DELETE;
+}
+
+void ZookeeperSessionTestBase::test_set_data(const std::string &path,
+ const std::string &data,
+ int expected_zerr)
+{
+ test_operate_node(path, zookeeper_session::ZOO_SET, data, expected_zerr);
+}
+
+void ZookeeperSessionTestBase::test_get_data(const std::string &path,
+ int expected_zerr,
+ const std::string &expected_data)
+{
+ std::string actual_data;
+ test_operate_node(
+ path,
+ zookeeper_session::ZOO_GET,
+ [&actual_data](zookeeper_session::zoo_opcontext *op) mutable {
+ actual_data.assign(op->_output.get_op.value,
op->_output.get_op.value_length);
+ },
+ expected_zerr);
+
+ if (expected_zerr != ZOK) {
+ return;
+ }
+
+ ASSERT_EQ(expected_data, actual_data);
+}
+
+void ZookeeperSessionTestBase::test_get_data(const std::string &path, int
expected_zerr)
+{
+ test_get_data(path, expected_zerr, "");
+}
+
+void ZookeeperSessionTestBase::test_exists_node(const std::string &path, int
expected_zerr)
+{
+ test_operate_node(path, zookeeper_session::ZOO_EXISTS, expected_zerr);
+}
+
+void ZookeeperSessionTestBase::get_sub_nodes(const std::string &path,
+ int &zerr,
+ std::vector<std::string>
&sub_nodes)
+{
+ utils::notify_event on_completed;
+
+ auto *op = zookeeper_session::create_context();
+ op->_optype = zookeeper_session::ZOO_GETCHILDREN;
+ op->_input._path = path;
+ op->_callback_function =
+ [&zerr, &sub_nodes, &on_completed](zookeeper_session::zoo_opcontext
*op) mutable {
+ zerr = op->_output.error;
+
+ const auto notifier = defer([&on_completed]() {
on_completed.notify(); });
+
+ if (zerr != ZOK) {
+ return;
+ }
+
+ const String_vector *strings = op->_output.getchildren_op.strings;
+
+ sub_nodes.clear();
+ sub_nodes.reserve(strings->count);
+ std::transform(strings->data,
+ strings->data + strings->count,
+ std::back_inserter(sub_nodes),
+ [](const char *str) { return std::string(str); });
+ };
+
+ _session->visit(op);
+ on_completed.wait();
+}
+
+void ZookeeperSessionTestBase::test_get_sub_nodes(const std::string &path,
+ int expected_zerr,
+ std::vector<std::string>
&&expected_sub_nodes)
+{
+ int actual_zerr{0};
+ std::vector<std::string> actual_sub_nodes;
+ get_sub_nodes(path, actual_zerr, actual_sub_nodes);
+
+ ASSERT_EQ(expected_zerr, actual_zerr)
+ << "expected_zerr = \"" << zerror(expected_zerr) << "\", actual_zerr =
\""
+ << zerror(actual_zerr) << "\", path = " << path;
+
+ std::sort(expected_sub_nodes.begin(), expected_sub_nodes.end());
+ std::sort(actual_sub_nodes.begin(), actual_sub_nodes.end());
+ ASSERT_EQ(expected_sub_nodes, actual_sub_nodes);
+}
+
+void ZookeeperSessionTestBase::test_no_node(const std::string &path)
+{
+ test_exists_node(path, ZNONODE);
+ test_get_data(path, ZNONODE);
+ test_get_sub_nodes(path, ZNONODE, std::vector<std::string>());
+}
+
+void ZookeeperSessionTestBase::test_has_data(const std::string &path, const
std::string &data)
+{
+ test_exists_node(path, ZOK);
+ test_get_data(path, ZOK, data);
+}
+
+} // namespace dsn::dist
diff --git a/src/zookeeper/test/zookeeper_session_test_base.h
b/src/zookeeper/test/zookeeper_session_test_base.h
new file mode 100644
index 000000000..9f63fde11
--- /dev/null
+++ b/src/zookeeper/test/zookeeper_session_test_base.h
@@ -0,0 +1,158 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+
+#include <gtest/gtest.h>
+#include <functional>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "utils/ports.h"
+#include "zookeeper/zookeeper_session.h"
+
+namespace dsn::dist {
+
+// As the base class for ZookeeperSessionTest, provides APIs used to test
connecting to
+// the ZooKeeper.
+class ZookeeperSessionConnector : public testing::Test
+{
+public:
+ ~ZookeeperSessionConnector() override = default;
+
+protected:
+ ZookeeperSessionConnector() = default;
+
+ void TearDown() override;
+
+ // Connect to ZooKeeper and test. After connected, `expected_zoo_state` is
the expected
+ // state.
+ void test_connect(int expected_zoo_state);
+
+ std::unique_ptr<zookeeper_session> _session;
+
+ DISALLOW_COPY_AND_ASSIGN(ZookeeperSessionConnector);
+ DISALLOW_MOVE_AND_ASSIGN(ZookeeperSessionConnector);
+};
+
+// As the base class for ZookeeperSessionTest, provides APIs used to test
performing
+// operations on the ZooKeeper.
+class ZookeeperSessionTestBase : public ZookeeperSessionConnector
+{
+public:
+ ~ZookeeperSessionTestBase() override = default;
+
+protected:
+ ZookeeperSessionTestBase() = default;
+
+ void SetUp() override;
+
+ void TearDown() override;
+
+ // Test creating the node `path` with `data`: the expected zoo error is
`expected_zerr`.
+ void test_create_node(const std::string &path, const std::string &data,
int expected_zerr);
+
+ // Test deleting the node `path`: the expected zoo error is
`expected_zerr`.
+ void test_delete_node(const std::string &path, int expected_zerr);
+
+ // Delete the node `path` whether it exists or not.
+ void test_delete_node(const std::string &path);
+
+ // Test updating the node `path` with `data`: the expected zoo error is
`expected_zerr`.
+ void test_set_data(const std::string &path, const std::string &data, int
expected_zerr);
+
+ // Test getting data from the node `path`: the expected zoo error and the
data it holds
+ // are `expected_zerr` and `expected_data`.
+ void
+ test_get_data(const std::string &path, int expected_zerr, const
std::string &expected_data);
+
+ // Test getting data from the node `path`: the expected zoo error is
`expected_zerr`
+ // while the data it holds is ignored.
+ void test_get_data(const std::string &path, int expected_zerr);
+
+ // Test whether the node `path` exists.
+ void test_exists_node(const std::string &path, int expected_zerr);
+
+ // Test getting the sub nodes from the node `path`: the expected zoo error
and the
+ // obtained sub nodes are `expected_zerr` and `expected_sub_nodes`.
+ void test_get_sub_nodes(const std::string &path,
+ int expected_zerr,
+ std::vector<std::string> &&expected_sub_nodes);
+
+ // Assert that the node `path` does not exist.
+ void test_no_node(const std::string &path);
+
+ // Assert that the node `path` holds `data`.
+ void test_has_data(const std::string &path, const std::string &data);
+
+private:
+ // Perform an operation on a ZooKeeper node synchronously:
+ // - path: the path of the ZooKeeper node.
+ // - op_type: the operation type.
+ // - data: used for ZOO_CREATE and ZOO_SET.
+ // - callback: called after the operation was finished (nothing would be
called if
+ // nullptr).
+ // - zerr: zoo error for the operation.
+ void operate_node(const std::string &path,
+ zookeeper_session::ZOO_OPERATION op_type,
+ const std::string &data,
+ std::function<void(zookeeper_session::zoo_opcontext *)>
&&callback,
+ int &zerr);
+
+ // The same as the above, except that `data` is empty and `callback` is
nullptr.
+ void operate_node(const std::string &path,
zookeeper_session::ZOO_OPERATION op_type, int &zerr);
+
+ // Test an operation on a ZooKeeper node:
+ // - path: the path of the ZooKeeper node.
+ // - op_type: the operation type.
+ // - data: used for ZOO_CREATE and ZOO_SET.
+ // - callback: called after the operation was finished (nothing would be
called if
+ // nullptr).
+ // - expected_zerr: expected zoo error for the operation.
+ void test_operate_node(const std::string &path,
+ zookeeper_session::ZOO_OPERATION op_type,
+ const std::string &data,
+ std::function<void(zookeeper_session::zoo_opcontext
*)> &&callback,
+ int expected_zerr);
+
+ // The same as the above, except that `callback` is nullptr.
+ void test_operate_node(const std::string &path,
+ zookeeper_session::ZOO_OPERATION op_type,
+ const std::string &data,
+ int expected_zerr);
+
+ // The same as the above, except that `data` is empty.
+ void test_operate_node(const std::string &path,
+ zookeeper_session::ZOO_OPERATION op_type,
+ std::function<void(zookeeper_session::zoo_opcontext
*)> &&callback,
+ int expected_zerr);
+
+ // The same as the above, except that `data` is empty and `callback` is
nullptr.
+ void test_operate_node(const std::string &path,
+ zookeeper_session::ZOO_OPERATION op_type,
+ int expected_zerr);
+
+ // Get the sub nodes of a ZooKeeper node:
+ // - path: the path of the ZooKeeper node.
+ // - zerr: zoo error for the operation.
+ // - sub_nodes: the obtained sub nodes.
+ void get_sub_nodes(const std::string &path, int &zerr,
std::vector<std::string> &sub_nodes);
+
+ DISALLOW_COPY_AND_ASSIGN(ZookeeperSessionTestBase);
+ DISALLOW_MOVE_AND_ASSIGN(ZookeeperSessionTestBase);
+};
+
+} // namespace dsn::dist
diff --git a/src/zookeeper/zookeeper_session.cpp
b/src/zookeeper/zookeeper_session.cpp
index dfff72b47..3e264f4f6 100644
--- a/src/zookeeper/zookeeper_session.cpp
+++ b/src/zookeeper/zookeeper_session.cpp
@@ -24,22 +24,37 @@
* THE SOFTWARE.
*/
+#include <openssl/bio.h>
+#include <openssl/evp.h>
+#include <openssl/types.h>
#include <sasl/sasl.h>
-#include <stdlib.h>
#include <zookeeper/zookeeper.h>
#include <algorithm>
+#include <cerrno>
+#include <cstdlib>
#include <utility>
-#include "runtime/app_model.h"
#include "rpc/rpc_address.h"
+#include "runtime/app_model.h"
+#include "utils/defer.h"
+#include "utils/enum_helper.h"
#include "utils/filesystem.h"
#include "utils/flags.h"
#include "utils/fmt_logging.h"
+#include "utils/safe_strerror_posix.h"
#include "utils/strings.h"
#include "zookeeper/proto.h"
#include "zookeeper/zookeeper.jute.h"
#include "zookeeper_session.h"
+#define INVALID_ZOO_LOG_LEVEL static_cast<ZooLogLevel>(0)
+ENUM_BEGIN(ZooLogLevel, INVALID_ZOO_LOG_LEVEL)
+ENUM_REG(ZOO_LOG_LEVEL_ERROR)
+ENUM_REG(ZOO_LOG_LEVEL_WARN)
+ENUM_REG(ZOO_LOG_LEVEL_INFO)
+ENUM_REG(ZOO_LOG_LEVEL_DEBUG)
+ENUM_END(ZooLogLevel)
+
DSN_DECLARE_bool(enable_zookeeper_kerberos);
DSN_DEFINE_string(security,
zookeeper_kerberos_service_name,
@@ -55,7 +70,8 @@ DSN_DEFINE_int32(zookeeper,
timeout_ms,
30000,
"The timeout of accessing ZooKeeper, in milliseconds");
-DSN_DEFINE_string(zookeeper, hosts_list, "", "Zookeeper hosts list");
+DSN_DEFINE_string(zookeeper, zoo_log_level, "ZOO_LOG_LEVEL_INFO", "ZooKeeper
log level");
+DSN_DEFINE_string(zookeeper, hosts_list, "", "ZooKeeper hosts list");
DSN_DEFINE_string(zookeeper, sasl_service_name, "zookeeper", "");
DSN_DEFINE_string(zookeeper,
sasl_service_fqdn,
@@ -71,6 +87,11 @@ DSN_DEFINE_string(zookeeper,
sasl_password_file,
"",
"File containing the password (recommended for
SASL/DIGEST-MD5)");
+DSN_DEFINE_string(zookeeper,
+ sasl_password_encryption_scheme,
+ "",
+ "If non-empty, specify the scheme in which the password is
encrypted; "
+ "otherwise, the password is unencrypted plaintext");
DSN_DEFINE_group_validator(enable_zookeeper_kerberos, [](std::string &message)
-> bool {
if (FLAGS_enable_zookeeper_kerberos &&
!dsn::utils::equals(FLAGS_sasl_mechanisms_type, "GSSAPI")) {
@@ -191,69 +212,135 @@ const char *zookeeper_session::string_zoo_state(int
zoo_state)
return "invalid_state";
}
-zookeeper_session::~zookeeper_session() {}
+zookeeper_session::zookeeper_session(service_app_info info)
+ : _info(std::move(info)), _handle(nullptr)
+{
+}
+
+namespace {
+
+int decode_base64(
+ const char *content, size_t content_len, char *buf, size_t buf_len, size_t
*passwd_len)
+{
+ if (content_len == 0) {
+ return SASL_BADPARAM;
+ }
+
+ BIO *bio = BIO_new_mem_buf(content, static_cast<int>(content_len));
+ if (bio == nullptr) {
+ return SASL_FAIL;
+ }
+
+ BIO *b64 = BIO_new(BIO_f_base64());
+ if (b64 == nullptr) {
+ BIO_free(bio);
+ return SASL_FAIL;
+ }
+
+ BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
+
+ bio = BIO_push(b64, bio);
+ const auto cleanup = dsn::defer([bio]() { BIO_free_all(bio); });
+
+ const int read_len = BIO_read(bio, buf, static_cast<int>(buf_len));
+ if (read_len <= 0) {
+ return SASL_FAIL;
+ }
+
+ *passwd_len = read_len;
+
+ return SASL_OK;
+}
+
+int zookeeper_password_decoder(const char *content,
+ size_t content_len,
+ void *context,
+ char *buf,
+ size_t buf_len,
+ size_t *passwd_len)
+{
+ if (dsn::utils::iequals(FLAGS_sasl_password_encryption_scheme, "base64")) {
+ return decode_base64(content, content_len, buf, buf_len, passwd_len);
+ }
+
+ return SASL_BADPARAM;
+}
-zookeeper_session::zookeeper_session(const service_app_info &node) :
_handle(nullptr)
+bool is_password_file_plaintext()
{
- _srv_node = node;
+ return dsn::utils::is_empty(FLAGS_sasl_password_encryption_scheme) ||
+ dsn::utils::iequals(FLAGS_sasl_password_encryption_scheme,
"plaintext");
+}
+
+zhandle_t *create_zookeeper_handle(watcher_fn watcher, void *context)
+{
+ const auto zoo_log_level = enum_from_string(FLAGS_zoo_log_level,
INVALID_ZOO_LOG_LEVEL);
+ CHECK(zoo_log_level != INVALID_ZOO_LOG_LEVEL, "Invalid zoo log level: {}",
FLAGS_zoo_log_level);
+ zoo_set_debug_level(zoo_log_level);
+
+ // SASL auth is enabled iff FLAGS_sasl_mechanisms_type is non-empty.
+ if (dsn::utils::is_empty(FLAGS_sasl_mechanisms_type)) {
+ return zookeeper_init(FLAGS_hosts_list, watcher, FLAGS_timeout_ms,
nullptr, context, 0);
+ }
+
+ const int err = sasl_client_init(nullptr);
+ CHECK_EQ_MSG(err,
+ SASL_OK,
+ "Unable to initialize SASL library {}",
+ sasl_errstring(err, nullptr, nullptr));
+
+ CHECK(!dsn::utils::is_empty(FLAGS_sasl_password_file),
+ "sasl_password_file must be specified when SASL auth is enabled");
+ CHECK(utils::filesystem::file_exists(FLAGS_sasl_password_file),
+ "sasl_password_file {} not exist!",
+ FLAGS_sasl_password_file);
+
+ const char *host = "";
+ if (!dsn::utils::is_empty(FLAGS_sasl_service_fqdn)) {
+ CHECK(dsn::rpc_address::from_host_port(FLAGS_sasl_service_fqdn),
+ "sasl_service_fqdn '{}' is invalid",
+ FLAGS_sasl_service_fqdn);
+ host = FLAGS_sasl_service_fqdn;
+ }
+
+ // DIGEST-MD5 requires '--server-fqdn zk-sasl-md5' for historical reasons
on ZooKeeper
+ // C client.
+ if (dsn::utils::equals(FLAGS_sasl_mechanisms_type, "DIGEST-MD5")) {
+ host = "zk-sasl-md5";
+ }
+
+ // Only encrypted passwords need their decoders.
+ zoo_sasl_password_t passwd = {FLAGS_sasl_password_file,
+ nullptr,
+ is_password_file_plaintext() ? nullptr
+ :
zookeeper_password_decoder};
+
+ zoo_sasl_params_t sasl_params = {};
+ sasl_params.service = FLAGS_sasl_service_name;
+ sasl_params.host = host;
+ sasl_params.mechlist = FLAGS_sasl_mechanisms_type;
+ sasl_params.callbacks =
+ zoo_sasl_make_password_callbacks(FLAGS_sasl_user_name,
FLAGS_sasl_realm, &passwd);
+
+ return zookeeper_init_sasl(
+ FLAGS_hosts_list, watcher, FLAGS_timeout_ms, nullptr, context, 0,
nullptr, &sasl_params);
}
+} // anonymous namespace
+
int zookeeper_session::attach(void *callback_owner, const state_callback &cb)
{
utils::auto_write_lock l(_watcher_lock);
- do {
- if (nullptr != _handle) {
- break;
- }
- if (utils::is_empty(FLAGS_sasl_mechanisms_type)) {
- _handle = zookeeper_init(
- FLAGS_hosts_list, global_watcher, FLAGS_timeout_ms, nullptr,
this, 0);
- break;
- }
- int err = sasl_client_init(nullptr);
- CHECK_EQ_MSG(err,
- SASL_OK,
- "Unable to initialize SASL library {}",
- sasl_errstring(err, nullptr, nullptr));
-
- if (!utils::is_empty(FLAGS_sasl_password_file)) {
- CHECK(utils::filesystem::file_exists(FLAGS_sasl_password_file),
- "sasl_password_file {} not exist!",
- FLAGS_sasl_password_file);
- }
- auto param_host = "";
- if (!utils::is_empty(FLAGS_sasl_service_fqdn)) {
- CHECK(dsn::rpc_address::from_host_port(FLAGS_sasl_service_fqdn),
- "sasl_service_fqdn '{}' is invalid",
- FLAGS_sasl_service_fqdn);
- param_host = FLAGS_sasl_service_fqdn;
- }
- // DIGEST-MD5 requires '--server-fqdn zk-sasl-md5' for historical
reasons on zk c client
- if (dsn::utils::equals(FLAGS_sasl_mechanisms_type, "DIGEST-MD5")) {
- param_host = "zk-sasl-md5";
- }
+ if (_handle == nullptr) {
+ // zookeeper_init* functions will set `errno` while initialization
failed due to some
+ // error.
+ errno = 0;
+ _handle = create_zookeeper_handle(global_watcher, this);
+ CHECK_NOTNULL(_handle, "zookeeper session init failed: {}",
utils::safe_strerror(errno));
+ }
- zoo_sasl_params_t sasl_params = {0};
- sasl_params.service = FLAGS_sasl_service_name;
- sasl_params.mechlist = FLAGS_sasl_mechanisms_type;
- sasl_params.host = param_host;
- sasl_params.callbacks = zoo_sasl_make_basic_callbacks(
- FLAGS_sasl_user_name, FLAGS_sasl_realm, FLAGS_sasl_password_file);
-
- _handle = zookeeper_init_sasl(FLAGS_hosts_list,
- global_watcher,
- FLAGS_timeout_ms,
- nullptr,
- this,
- 0,
- nullptr,
- &sasl_params);
- } while (false);
-
- CHECK_NOTNULL(_handle, "zookeeper session init failed");
-
- _watchers.push_back(watcher_object());
+ _watchers.emplace_back();
_watchers.back().watcher_path = "";
_watchers.back().callback_owner = callback_owner;
_watchers.back().watcher_callback = cb;
@@ -379,7 +466,7 @@ void zookeeper_session::init_non_dsn_thread()
{
static __thread int dsn_context_init = 0;
if (dsn_context_init == 0) {
- dsn_mimic_app(_srv_node.role_name.c_str(), _srv_node.index);
+ dsn_mimic_app(_info.role_name.c_str(), _info.index);
dsn_context_init = 1;
}
}
diff --git a/src/zookeeper/zookeeper_session.h
b/src/zookeeper/zookeeper_session.h
index e46f6d6a2..5fcaa9f9a 100644
--- a/src/zookeeper/zookeeper_session.h
+++ b/src/zookeeper/zookeeper_session.h
@@ -24,9 +24,9 @@
* THE SOFTWARE.
*/
-#include <stdint.h>
-#include <string.h>
#include <zookeeper/zookeeper.h>
+#include <cstdint>
+#include <cstring>
#include <functional>
#include <list>
#include <memory>
@@ -37,12 +37,12 @@
#include "utils/autoref_ptr.h"
#include "utils/blob.h"
#include "utils/fmt_utils.h"
+#include "utils/ports.h"
#include "utils/synchronize.h"
struct String_vector;
-namespace dsn {
-namespace dist {
+namespace dsn::dist {
// A C++ wrapper of zookeeper c async APIs.
class zookeeper_session
@@ -164,10 +164,10 @@ public:
static const char *string_zoo_event(int zoo_event);
static const char *string_zoo_state(int zoo_state);
-public:
- typedef std::function<void(int)> state_callback;
- zookeeper_session(const service_app_info &info);
- ~zookeeper_session();
+ using state_callback = std::function<void(int)>;
+
+ explicit zookeeper_session(service_app_info info);
+ ~zookeeper_session() = default;
int attach(void *callback_owner, const state_callback &cb);
void detach(void *callback_owner);
@@ -177,6 +177,7 @@ public:
private:
utils::rw_lock_nr _watcher_lock;
+
struct watcher_object
{
std::string watcher_path;
@@ -184,7 +185,8 @@ private:
state_callback watcher_callback;
};
std::list<watcher_object> _watchers;
- service_app_info _srv_node;
+
+ service_app_info _info;
zhandle_t *_handle;
void dispatch_event(int type, int zstate, const char *path);
@@ -196,8 +198,11 @@ private:
static void
global_strings_completion(int rc, const struct String_vector *strings,
const void *data);
static void global_void_completion(int rc, const void *data);
+
+ DISALLOW_COPY_AND_ASSIGN(zookeeper_session);
+ DISALLOW_MOVE_AND_ASSIGN(zookeeper_session);
};
-} // namespace dist
-} // namespace dsn
+
+} // namespace dsn::dist
USER_DEFINED_STRUCTURE_FORMATTER(::dsn::dist::zookeeper_session);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]