From: Yehuda Sadeh Date: Fri, 5 Jan 2018 10:42:06 +0000 (-0800) Subject: ceph_json: formattable, set, erase, unitest X-Git-Tag: v13.1.0~270^2~37 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=76b79cb8cb302fabaa1d4db022958009952f4a8b;p=ceph.git ceph_json: formattable, set, erase, unitest Extend the formattable api to provide a mechanism to set and erase entities by a string key that references them. E.g., "foo.bar[123].xyz" Signed-off-by: Yehuda Sadeh --- diff --git a/src/common/ceph_json.cc b/src/common/ceph_json.cc index 13ddcf4f78af..262019896aaf 100644 --- a/src/common/ceph_json.cc +++ b/src/common/ceph_json.cc @@ -6,6 +6,7 @@ #include #include +#include using namespace json_spirit; @@ -225,10 +226,18 @@ bool JSONParser::parse(const char *buf_, int len) string json_string(buf_, len); success = read(json_string, data); - if (success) + if (success) { handle_value(data); - else + if (data.type() != obj_type && + data.type() != array_type) { + if (data.type() == str_type) + data_string = data.get_str(); + else + data_string = write(data, raw_utf8); + } + } else { set_failure(); + } return success; } @@ -534,6 +543,42 @@ const JSONFormattable& JSONFormattable::operator[](const string& name) const return i->second; } +const JSONFormattable& JSONFormattable::operator[](size_t index) const +{ + if (index >= arr.size()) { + return default_formattable; + } + return arr[index]; +} + +JSONFormattable& JSONFormattable::operator[](const string& name) +{ + auto i = obj.find(name); + if (i == obj.end()) { + return default_formattable; + } + return i->second; +} + +JSONFormattable& JSONFormattable::operator[](size_t index) +{ + if (index >= arr.size()) { + return default_formattable; + } + return arr[index]; +} + +bool JSONFormattable::exists(const string& name) const +{ + auto i = obj.find(name); + return (i != obj.end()); +} + +bool JSONFormattable::exists(size_t index) const +{ + return (index < arr.size()); +} + bool JSONFormattable::find(const string& name, string *val) const { auto i = obj.find(name); @@ -591,6 +636,163 @@ bool JSONFormattable::get_bool(const string& name, bool def_val) const return (*this)[name].def(def_val); } +struct field_entity { + bool is_obj{false}; /* either obj field or array entity */ + string name; /* if obj */ + int index{0}; /* if array */ + + field_entity() {} + field_entity(const string& n) : is_obj(true), name(n) {} + field_entity(int i) : is_obj(false), index(i) {} +}; + +static int parse_entity(const string& s, vector *result) +{ + size_t ofs = 0; + + while (ofs < s.size()) { + size_t next_arr = s.find('[', ofs); + if (next_arr == string::npos) { + if (ofs != 0) { + return -EINVAL; + } + result->push_back(field_entity(s)); + return 0; + } + if (next_arr > ofs) { + string field = s.substr(ofs, next_arr - ofs); + result->push_back(field_entity(field)); + ofs = next_arr; + } + size_t end_arr = s.find(']', next_arr + 1); + if (end_arr == string::npos) { + return -EINVAL; + } + + string index_str = s.substr(next_arr + 1, end_arr - next_arr - 1); + + ofs = end_arr + 1; + + result->push_back(field_entity(atoi(index_str.c_str()))); + } + return 0; +} + +int JSONFormattable::set(const string& name, const string& val) +{ + boost::escaped_list_separator els('\\', '.', '"'); + boost::tokenizer > tok(name, els); + + JSONFormattable *f = this; + + JSONParser jp; + + bool is_json = jp.parse(val.c_str(), val.size()); + + for (auto i : tok) { + vector v; + int ret = parse_entity(i, &v); + if (ret < 0) { + return ret; + } + for (auto vi : v) { + if (f->type == FMT_NONE) { + if (vi.is_obj) { + f->type = FMT_OBJ; + } else { + f->type = FMT_ARRAY; + } + } + + if (f->type == FMT_OBJ) { + if (!vi.is_obj) { + return -EINVAL; + } + f = &f->obj[vi.name]; + } else if (f->type == FMT_ARRAY) { + if (vi.is_obj) { + return -EINVAL; + } + if ((size_t)vi.index >= f->arr.size()) { + f->arr.resize(vi.index + 1); + } + f = &f->arr[vi.index]; + } + } + } + + if (is_json) { + f->decode_json(&jp); + } else { + f->type = FMT_STRING; + f->str = val; + } + + return 0; +} + +int JSONFormattable::erase(const string& name) +{ + boost::escaped_list_separator els('\\', '.', '"'); + boost::tokenizer > tok(name, els); + + JSONFormattable *f = this; + JSONFormattable *parent = nullptr; + field_entity last_entity; + + for (auto i : tok) { + vector v; + int ret = parse_entity(i, &v); + if (ret < 0) { + return ret; + } + for (auto vi : v) { + if (f->type == FMT_NONE) { + if (vi.is_obj) { + f->type = FMT_OBJ; + } else { + f->type = FMT_ARRAY; + } + } + + parent = f; + + last_entity = vi; + + if (f->type == FMT_OBJ) { + if (!vi.is_obj) { + return -EINVAL; + } + auto iter = f->obj.find(vi.name); + if (iter == f->obj.end()) { + return 0; /* nothing to erase */ + } + f = &iter->second; + } else if (f->type == FMT_ARRAY) { + if (vi.is_obj) { + return -EINVAL; + } + if ((size_t)vi.index >= f->arr.size()) { + return 0; /* index beyond array boundaries */ + } + f = &f->arr[vi.index]; + } + } + } + + if (!parent) { + *this = JSONFormattable(); /* erase everything */ + } else { + if (last_entity.is_obj) { + parent->obj.erase(last_entity.name); + } else { + parent->arr.erase(parent->arr.begin() + last_entity.index); + } + } + + return 0; +} + void encode_json(const char *name, const JSONFormattable& v, Formatter *f) { switch (v.type) { diff --git a/src/common/ceph_json.h b/src/common/ceph_json.h index a3b3a8b307a8..308abe867cf8 100644 --- a/src/common/ceph_json.h +++ b/src/common/ceph_json.h @@ -527,9 +527,10 @@ struct JSONFormattable { } const JSONFormattable& operator[](const std::string& name) const; - const JSONFormattable& operator[](const char * name) const { - return this->operator[](std::string(name)); - } + const JSONFormattable& operator[](size_t index) const; + + JSONFormattable& operator[](const std::string& name); + JSONFormattable& operator[](size_t index); operator std::string() const { return str; @@ -548,6 +549,11 @@ struct JSONFormattable { return this->operator[](name)(T()); } + template + T operator[](const std::string& name) { + return this->operator[](name)(T()); + } + string operator ()(const char *def_val) const { return def(string(def_val)); } @@ -564,6 +570,9 @@ struct JSONFormattable { return def(def_val); } + bool exists(const string& name) const; + bool exists(size_t index) const; + std::string def(const std::string& def_val) const; int def(int def_val) const; bool def(bool def_val) const; @@ -571,8 +580,12 @@ struct JSONFormattable { bool find(const std::string& name, std::string *val) const; std::string get(const std::string& name, const std::string& def_val) const; + int get_int(const std::string& name, int def_val) const; bool get_bool(const std::string& name, bool def_val) const; + + int set(const string& name, const string& val); + int erase(const string& name); }; WRITE_CLASS_ENCODER(JSONFormattable) diff --git a/src/rgw/rgw_admin.cc b/src/rgw/rgw_admin.cc index 943ca93525a7..3b7439a14652 100644 --- a/src/rgw/rgw_admin.cc +++ b/src/rgw/rgw_admin.cc @@ -2301,12 +2301,36 @@ static void sync_status(Formatter *formatter) static void parse_tier_config_param(const string& s, map& out) { + int level = 0; + string cur_conf; list confs; - get_str_list(s, ",", confs); + for (auto c : s) { + switch (c) { + case '{': + ++level; + break; + case '}': + --level; + break; + case ',': + if (level == 0) { + confs.push_back(cur_conf); + } + cur_conf.clear(); + break; + default: + cur_conf += c; + break; + }; + } + if (!cur_conf.empty()) { + confs.push_back(cur_conf); + } + for (auto c : confs) { ssize_t pos = c.find("="); if (pos < 0) { - out[c] = ""; + out[""] = c; } else { out[c.substr(0, pos)] = c.substr(pos + 1); } @@ -3646,7 +3670,11 @@ int main(int argc, const char **argv) string *ptier_type = (tier_type_specified ? &tier_type : nullptr); for (auto a : tier_config_add) { - zone.tier_config.set(a.first, a.second); + int r = zone.tier_config.set(a.first, a.second); + if (r < 0) { + cerr << "ERROR: failed to set configurable: " << a << std::endl; + return EINVAL; + } } bool *psync_from_all = (sync_from_all_specified ? &sync_from_all : nullptr); @@ -4063,7 +4091,13 @@ int main(int argc, const char **argv) zone.system_key.id = access_key; zone.system_key.key = secret_key; zone.realm_id = realm_id; - zone.tier_config = tier_config_add; + for (auto a : tier_config_add) { + int r = zone.tier_config.set(a.first, a.second); + if (r < 0) { + cerr << "ERROR: failed to set configurable: " << a << std::endl; + return EINVAL; + } + } ret = zone.create(); if (ret < 0) { @@ -4324,7 +4358,7 @@ int main(int argc, const char **argv) if (tier_config_add.size() > 0) { for (auto add : tier_config_add) { - zone.tier_config[add.first] = add.second; + zone.tier_config.set(add.first, add.second); } need_zone_update = true; } diff --git a/src/test/common/CMakeLists.txt b/src/test/common/CMakeLists.txt index 25361405898f..702cb647d2c7 100644 --- a/src/test/common/CMakeLists.txt +++ b/src/test/common/CMakeLists.txt @@ -60,6 +60,7 @@ add_executable(unittest_json_formattable test_json_formattable.cc ) add_ceph_unittest(unittest_json_formattable ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/unittest_json_formattable) +# add_dependencies(unittest_json_formattable ceph-common) target_link_libraries(unittest_json_formattable ceph-common global ${BLKID_LIBRARIES}) # unittest_sharedptr_registry diff --git a/src/test/common/test_json_formattable.cc b/src/test/common/test_json_formattable.cc index 3161f75d61f1..6042a1a55ba8 100644 --- a/src/test/common/test_json_formattable.cc +++ b/src/test/common/test_json_formattable.cc @@ -173,3 +173,97 @@ TEST(formatable, json_encode) { } +TEST(formatable, set) { + JSONFormattable f, f2; + + f.set("", "{ \"abc\": \"xyz\"}"); + ASSERT_EQ((string)f["abc"], "xyz"); + + f.set("aaa", "111"); + ASSERT_EQ((string)f["abc"], "xyz"); + ASSERT_EQ((int)f["aaa"], 111); + + f.set("obj", "{ \"a\": \"10\", \"b\": \"20\"}"); + ASSERT_EQ((int)f["obj"]["a"], 10); + ASSERT_EQ((int)f["obj"]["b"], 20); + + f.set("obj.c", "30"); + + ASSERT_EQ((int)f["obj"]["c"], 30); +} + +TEST(formatable, erase) { + JSONFormattable f, f2; + + f.set("", "{ \"abc\": \"xyz\"}"); + ASSERT_EQ((string)f["abc"], "xyz"); + + f.set("aaa", "111"); + ASSERT_EQ((string)f["abc"], "xyz"); + ASSERT_EQ((int)f["aaa"], 111); + f.erase("aaa"); + ASSERT_EQ((int)f["aaa"], 0); + + f.set("obj", "{ \"a\": \"10\", \"b\": \"20\"}"); + ASSERT_EQ((int)f["obj"]["a"], 10); + ASSERT_EQ((int)f["obj"]["b"], 20); + f.erase("obj.a"); + ASSERT_EQ((int)f["obj"]["a"], 0); + ASSERT_EQ((int)f["obj"]["b"], 20); +} + +static void dumpf(const JSONFormattable& f) +{ + JSONFormatter formatter; + formatter.open_object_section("bla"); + ::encode_json("f", f, &formatter); + formatter.close_section(); + formatter.flush(cout); +} + +TEST(formatable, set_array) { + JSONFormattable f, f2; + + f.set("asd[0]", "\"xyz\""); + ASSERT_EQ(f["asd"].array().size(), 1); + ASSERT_EQ((string)f["asd"][0], "xyz"); + + f.set("bbb[0][0]", "10"); + f.set("bbb[0][1]", "20"); + ASSERT_EQ(f["bbb"].array().size(), 1); + ASSERT_EQ(f["bbb"][0].array().size(), 2); + ASSERT_EQ((string)f["bbb"][0][0], "10"); + ASSERT_EQ((int)f["bbb"][0][1], 20); + f.set("bbb[0][1]", "25"); + ASSERT_EQ(f["bbb"][0].array().size(), 2); + ASSERT_EQ((int)f["bbb"][0][1], 25); + + f.set("foo.asd[0][0]", "{ \"field\": \"xyz\"}"); + ASSERT_EQ((string)f["foo"]["asd"][0][0]["field"], "xyz"); + + ASSERT_EQ(f.set("foo[0]", "\"zzz\""), -EINVAL); /* can't assign array to an obj entity */ + + f2.set("[0]", "{ \"field\": \"xyz\"}"); + ASSERT_EQ((string)f2[0]["field"], "xyz"); +} + +TEST(formatable, erase_array) { + JSONFormattable f; + + f.set("asd[0]", "\"xyz\""); + ASSERT_EQ(f["asd"].array().size(), 1); + ASSERT_EQ((string)f["asd"][0], "xyz"); + ASSERT_TRUE(f["asd"].exists(0)); + f.erase("asd[0]"); + ASSERT_FALSE(f["asd"].exists(0)); + f.set("asd[0]", "\"xyz\""); + ASSERT_TRUE(f["asd"].exists(0)); + f["asd"].erase("[0]"); + ASSERT_FALSE(f["asd"].exists(0)); + f.set("asd[0]", "\"xyz\""); + ASSERT_TRUE(f["asd"].exists(0)); + f.erase("asd"); + ASSERT_FALSE(f["asd"].exists(0)); + ASSERT_FALSE(f.exists("asd")); +} +