Skip to content

Commit

Permalink
[util] Introduce yaml-cpp to read config files
Browse files Browse the repository at this point in the history
KUDU-1948 notice that yaml is better than json when use it as a config
file.
yaml-cpp [https://github.com/jbeder/yaml-cpp] is the most popular C++
yaml library I can find on GitHub.
This patch introduce yaml-cpp into Kudu, do some simple wrap, and add
some unit tests.

Change-Id: I8ef58befaffbcc880e13fa6fec61b8e94a189b5a
Reviewed-on: http://gerrit.cloudera.org:8080/13294
Reviewed-by: Adar Dembo <[email protected]>
Tested-by: Kudu Jenkins
  • Loading branch information
acelyc111 authored and adembo committed May 15, 2019
1 parent 39a5987 commit 4ac5e50
Show file tree
Hide file tree
Showing 10 changed files with 495 additions and 1 deletion.
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1222,6 +1222,13 @@ else()
set(KRB5_REALM_OVERRIDE -Wl,-U,krb5_realm_override_loaded krb5_realm_override)
endif()

## yaml
find_package(Yaml REQUIRED)
include_directories(SYSTEM ${YAML_INCLUDE_DIR})
ADD_THIRDPARTY_LIB(yaml
STATIC_LIB "${YAML_STATIC_LIB}"
SHARED_LIB "${YAML_SHARED_LIB}")

## Boost

# We use a custom cmake module and not cmake's FindBoost.
Expand Down
31 changes: 31 additions & 0 deletions cmake_modules/FindYaml.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# 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.

find_path(YAML_INCLUDE_DIR yaml-cpp/yaml.h
# make sure we don't accidentally pick up a different version
NO_CMAKE_SYSTEM_PATH
NO_SYSTEM_ENVIRONMENT_PATH)
find_library(YAML_STATIC_LIB libyaml-cpp.a
NO_CMAKE_SYSTEM_PATH
NO_SYSTEM_ENVIRONMENT_PATH)
find_library(YAML_SHARED_LIB libyaml-cpp.so
NO_CMAKE_SYSTEM_PATH
NO_SYSTEM_ENVIRONMENT_PATH)

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(YAML REQUIRED_VARS
YAML_STATIC_LIB YAML_SHARED_LIB YAML_INCLUDE_DIR)
4 changes: 3 additions & 1 deletion src/kudu/util/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ set(UTIL_SRCS
version_info.cc
version_util.cc
website_util.cc
yamlreader.cc
zlib.cc
)

Expand Down Expand Up @@ -257,6 +258,7 @@ set(UTIL_LIBS
pb_util_proto
protobuf
version_info_proto
yaml
zlib)

if(NOT APPLE)
Expand Down Expand Up @@ -293,7 +295,6 @@ set(UTIL_COMPRESSION_SRCS
set(UTIL_COMPRESSION_LIBS
kudu_util
util_compression_proto

glog
gutil
lz4
Expand Down Expand Up @@ -467,6 +468,7 @@ ADD_KUDU_TEST(ttl_cache-test)
ADD_KUDU_TEST(url-coding-test)
ADD_KUDU_TEST(user-test)
ADD_KUDU_TEST(version_util-test)
ADD_KUDU_TEST(yamlreader-test)

if (NOT APPLE)
ADD_KUDU_TEST(minidump-test)
Expand Down
176 changes: 176 additions & 0 deletions src/kudu/util/yamlreader-test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// 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 "kudu/util/yamlreader.h"

#include <memory>
#include <string>
#include <vector>

#include <gtest/gtest.h>
#include <yaml-cpp/node/impl.h>
#include <yaml-cpp/node/node.h>

#include "kudu/util/env.h"
#include "kudu/util/slice.h"
#include "kudu/util/status.h"
#include "kudu/util/test_macros.h"
#include "kudu/util/test_util.h"

using std::string;
using std::unique_ptr;
using std::vector;

namespace kudu {

class YamlReaderTest : public KuduTest {
public:
Status GenerateYamlReader(const string& content, unique_ptr<YamlReader>* result) {
string fname = GetTestPath("YamlReaderTest.json");
unique_ptr<WritableFile> writable_file;
RETURN_NOT_OK(env_->NewWritableFile(fname, &writable_file));
RETURN_NOT_OK(writable_file->Append(Slice(content)));
RETURN_NOT_OK(writable_file->Close());
result->reset(new YamlReader(fname));
return Status::OK();
}
};

TEST_F(YamlReaderTest, FileNotExist) {
YamlReader r("YamlReaderTest.NotExist");
Status s = r.Init();
ASSERT_TRUE(s.IsCorruption());
ASSERT_STR_CONTAINS(
s.ToString(), "YAML::LoadFile error");
}

TEST_F(YamlReaderTest, Corruption) {
unique_ptr<YamlReader> r;
ASSERT_OK(GenerateYamlReader("foo", &r));
ASSERT_OK(r->Init());
int val = 0;
ASSERT_TRUE(YamlReader::ExtractScalar(r->node(), "foo", &val).IsCorruption());
}

TEST_F(YamlReaderTest, EmptyFile) {
unique_ptr<YamlReader> r;
ASSERT_OK(GenerateYamlReader("", &r));
ASSERT_OK(r->Init());

int val = 0;
ASSERT_TRUE(YamlReader::ExtractScalar(r->node(), "foo", &val).IsCorruption());
}

TEST_F(YamlReaderTest, KeyNotExist) {
unique_ptr<YamlReader> r;
ASSERT_OK(GenerateYamlReader("foo: 1", &r));
ASSERT_OK(r->Init());

int val = 0;
ASSERT_TRUE(YamlReader::ExtractScalar(r->node(), "bar", &val).IsNotFound());
}

TEST_F(YamlReaderTest, Scalar) {
{
unique_ptr<YamlReader> r;
ASSERT_OK(GenerateYamlReader("bool_val: false", &r));
ASSERT_OK(r->Init());
bool val = true;
ASSERT_OK(YamlReader::ExtractScalar(r->node(), "bool_val", &val));
ASSERT_EQ(val, false);
}

{
unique_ptr<YamlReader> r;
ASSERT_OK(GenerateYamlReader("int_val: 123", &r));
ASSERT_OK(r->Init());
int val = 0;
ASSERT_OK(YamlReader::ExtractScalar(r->node(), "int_val", &val));
ASSERT_EQ(val, 123);
}

{
unique_ptr<YamlReader> r;
ASSERT_OK(GenerateYamlReader("double_val: 123.456", &r));
ASSERT_OK(r->Init());
double val = 0.0;
ASSERT_OK(YamlReader::ExtractScalar(r->node(), "double_val", &val));
ASSERT_EQ(val, 123.456);
}

{
unique_ptr<YamlReader> r;
ASSERT_OK(GenerateYamlReader("string_val: hello yaml", &r));
ASSERT_OK(r->Init());
string val;
ASSERT_OK(YamlReader::ExtractScalar(r->node(), "string_val", &val));
ASSERT_EQ(val, "hello yaml");
}
}

TEST_F(YamlReaderTest, Map) {
unique_ptr<YamlReader> r;
ASSERT_OK(GenerateYamlReader(
"map_val: { key1: hello yaml, key2: 123.456 , key3: 123, key4: false}", &r));
ASSERT_OK(r->Init());

YAML::Node node;
ASSERT_OK(YamlReader::ExtractMap(r->node(), "map_val", &node));

{
string val;
ASSERT_OK(YamlReader::ExtractScalar(&node, "key1", &val));
ASSERT_EQ(val, "hello yaml");
}

{
double val = 0.0;
ASSERT_OK(YamlReader::ExtractScalar(&node, "key2", &val));
ASSERT_EQ(val, 123.456);
}

{
int val = 0;
ASSERT_OK(YamlReader::ExtractScalar(&node, "key3", &val));
ASSERT_EQ(val, 123);
}

{
bool val = true;
ASSERT_OK(YamlReader::ExtractScalar(&node, "key4", &val));
ASSERT_EQ(val, false);
}

// Not exist key.
{
int val = 0;
ASSERT_TRUE(YamlReader::ExtractScalar(&node, "key5", &val).IsNotFound());
}
}

TEST_F(YamlReaderTest, Array) {
unique_ptr<YamlReader> r;
ASSERT_OK(GenerateYamlReader("list_val: [1, 3, 5, 7, 9]", &r));
ASSERT_OK(r->Init());
const std::vector<int>& expect_vals = { 1, 3, 5, 7, 9 };

std::vector<int> vals;
ASSERT_OK(YamlReader::ExtractArray(r->node(), "list_val", &vals));
ASSERT_EQ(vals, expect_vals);
}

} // namespace kudu
100 changes: 100 additions & 0 deletions src/kudu/util/yamlreader.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// 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 "kudu/util/yamlreader.h"

#include <ostream>
#include <utility>

#include <glog/logging.h>
// IWYU pragma: no_include <yaml-cpp/node/detail/impl.h>
// IWYU pragma: no_include <yaml-cpp/node/parse.h>

#include "kudu/gutil/port.h"
#include "kudu/gutil/strings/substitute.h"

using std::string;
using strings::Substitute;
using YAML::Node;
using YAML::NodeType;

namespace kudu {

YamlReader::YamlReader(string filename) : filename_(std::move(filename)) {}

Status YamlReader::Init() {
try {
node_ = YAML::LoadFile(filename_);
} catch (std::exception& e) {
return Status::Corruption(Substitute("YAML::LoadFile error: $0", e.what()));
}

return Status::OK();
}

Status YamlReader::ExtractMap(const Node* node,
const string& field,
Node* result) {
CHECK(result);
Node val;
RETURN_NOT_OK(ExtractField(node, field, &val));
if (PREDICT_FALSE(!val.IsMap())) {
return Status::Corruption(Substitute(
"wrong type during field extraction: expected map but got $0",
TypeToString(val.Type())));
}
*result = val;
return Status::OK();
}

Status YamlReader::ExtractField(const Node* node,
const string& field,
Node* result) {
if (PREDICT_FALSE(!node->IsDefined() || !node->IsMap())) {
return Status::Corruption("node is not map type");
}
try {
*result = (*node)[field];
} catch (std::exception& e) {
return Status::NotFound(Substitute("parse field $0 error: $1", field, e.what()));
}
if (PREDICT_FALSE(!result->IsDefined())) {
return Status::Corruption("Missing field", field);
}

return Status::OK();
}

const char* YamlReader::TypeToString(NodeType::value t) {
switch (t) {
case NodeType::Undefined:
return "undefined";
case NodeType::Null:
return "null";
case NodeType::Scalar:
return "scalar";
case NodeType::Sequence:
return "sequence";
case NodeType::Map:
return "map";
default:
LOG(FATAL) << "unexpected type: " << t;
}
return "";
}

} // namespace kudu
Loading

0 comments on commit 4ac5e50

Please sign in to comment.