]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
ceph_json: formattable, set, erase, unitest
authorYehuda Sadeh <yehuda@redhat.com>
Fri, 5 Jan 2018 10:42:06 +0000 (02:42 -0800)
committerYehuda Sadeh <yehuda@redhat.com>
Thu, 12 Apr 2018 22:38:37 +0000 (15:38 -0700)
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 <yehuda@redhat.com>
src/common/ceph_json.cc
src/common/ceph_json.h
src/rgw/rgw_admin.cc
src/test/common/CMakeLists.txt
src/test/common/test_json_formattable.cc

index 13ddcf4f78af6020748f54aa19bfa899cff05e68..262019896aaffc96247d0f0cbb20fd9e35c0daaa 100644 (file)
@@ -6,6 +6,7 @@
 #include <include/types.h>
 
 #include <boost/algorithm/string.hpp>
+#include <boost/tokenizer.hpp>
 
 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<field_entity> *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<char> els('\\', '.', '"');
+  boost::tokenizer<boost::escaped_list_separator<char> > tok(name, els);
+
+  JSONFormattable *f = this;
+
+  JSONParser jp;
+
+  bool is_json = jp.parse(val.c_str(), val.size());
+
+  for (auto i : tok) {
+    vector<field_entity> 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<char> els('\\', '.', '"');
+  boost::tokenizer<boost::escaped_list_separator<char> > tok(name, els);
+
+  JSONFormattable *f = this;
+  JSONFormattable *parent = nullptr;
+  field_entity last_entity;
+
+  for (auto i : tok) {
+    vector<field_entity> 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) {
index a3b3a8b307a8604f397ff07ac49ef2a287d722bb..308abe867cf8f94119d601257c4cd900c18d1f2f 100644 (file)
@@ -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<class T>
+  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)
 
index 943ca93525a715ea720d3834edc76077492c8eb7..3b7439a14652d9b6c575f7cffb30e0e664a79c39 100644 (file)
@@ -2301,12 +2301,36 @@ static void sync_status(Formatter *formatter)
 
 static void parse_tier_config_param(const string& s, map<string, string, ltstr_nocase>& out)
 {
+  int level = 0;
+  string cur_conf;
   list<string> 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;
         }
index 25361405898f8fcfc51ff01ac359c42496816682..702cb647d2c7395dda418756a732eff02f38a9d8 100644 (file)
@@ -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
index 3161f75d61f134c4b53966b6c7876c0e30c27c35..6042a1a55ba8562fde9a0a520cbdce6c1bbf442b 100644 (file)
@@ -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"));
+}
+