MB-23267: Add ability to hide inapplicable config options 61/75961/10
authorJames Harrison <00jamesh@gmail.com>
Tue, 28 Mar 2017 12:46:12 +0000 (13:46 +0100)
committerDave Rigby <daver@couchbase.com>
Mon, 10 Apr 2017 10:09:49 +0000 (10:09 +0000)
This is expressed by declaring the requirements for a particular config
option in configuration.json e.g.,

"ephemeral_full_policy": {
    ...
    "requirements": {
        "bucket_type": "ephemeral"
    }
}

This example prevents "ephemeral_full_policy" from being listed in stats
if "bucket_type" is not "ephemeral".

Change-Id: I4c85132612f55a4edb7c5497c9744ef63efbd206
Reviewed-on: http://review.couchbase.org/75961
Tested-by: Build Bot <build@couchbase.com>
Reviewed-by: Dave Rigby <daver@couchbase.com>
src/configuration.cc
src/configuration.h
src/configuration_impl.h [new file with mode: 0644]
tools/genconfig.cc

index 2afcd47..7f94dfd 100644 (file)
 #include "config.h"
 #include "configuration.h"
 
-#include <boost/variant.hpp>
-#include <platform/cb_malloc.h>
-
-#include <algorithm>
-#include <sstream>
-#include <vector>
+#include "configuration_impl.h"
 
 #ifdef AUTOCONF_BUILD
 #include "generated_configuration.cc"
@@ -57,11 +52,12 @@ Configuration::Configuration() {
 struct Configuration::value_t {
     std::vector<std::unique_ptr<ValueChangedListener>> changeListener;
     std::unique_ptr<ValueChangedValidator> validator;
+    std::unique_ptr<Requirement> requirement;
 
     // At the moment, the order of these template parameters must
     // match the order of the types in config_datatype. Looking
     // for a cleaner method.
-    boost::variant<size_t, ssize_t, float, bool, std::string> value;
+    value_variant_t value;
 
     std::vector<ValueChangedListener*> copyListeners() {
         std::vector<ValueChangedListener*> copy;
@@ -170,9 +166,51 @@ ValueChangedValidator *Configuration::setValueValidator(const std::string &key,
     return ret;
 }
 
+Requirement* Configuration::setRequirements(const std::string& key,
+                                            Requirement* requirement) {
+    Requirement* ret = nullptr;
+    LockHolder lh(mutex);
+    if (attributes.find(key) != attributes.end()) {
+        ret = attributes[key]->requirement.release();
+        attributes[key]->requirement.reset(requirement);
+    }
+
+    return ret;
+}
+
+bool Configuration::requirementsMet(const value_t& value) const {
+    if (value.requirement) {
+        for (auto requirement : value.requirement->requirements) {
+            const auto iter = attributes.find(requirement.first);
+            if (iter == attributes.end()) {
+                // Parameter does not exist, returning true assuming the config
+                // is not yet complete. We cannot verify yet.
+                return true;
+            }
+            if (iter->second->value != requirement.second) {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+void Configuration::requirementsMetOrThrow(const std::string& key) const {
+    LockHolder lh(mutex);
+    auto itr = attributes.find(key);
+    if (itr != attributes.end()) {
+        if (!requirementsMet(*(itr->second))) {
+            throw requirements_unsatisfied("Cannot set" + key +
+                                           " : requirements not met");
+        }
+    }
+}
+
 void Configuration::addStats(ADD_STAT add_stat, const void *c) const {
     LockHolder lh(mutex);
     for (const auto& attribute :  attributes) {
+        if (!requirementsMet(*attribute.second)) {
+            continue;
+        }
         std::stringstream value;
         value << std::boolalpha << attribute.second->value;
         std::stringstream key;
index 730e875..9bd61c5 100644 (file)
@@ -361,12 +361,16 @@ private:
     std::set<std::string> acceptable;
 };
 
+class Requirement;
+
 /**
  * The configuration class represents and provides access to the
  * entire configuration of the server.
  */
 class Configuration {
 public:
+    struct value_t;
+
     Configuration();
     ~Configuration();
 
@@ -426,6 +430,20 @@ public:
      */
     void addAlias(const std::string& key, const std::string& alias);
 
+    /**
+     * Adds a prerequisite to a configuration option. This must be satisfied
+     * in order to set/get the config value or for it to appear in stats.
+     *
+     * @param key the key to set the requirement for
+     * @param requirement the requirement
+     */
+    Requirement* setRequirements(const std::string& key,
+                                 Requirement* requirement);
+
+    bool requirementsMet(const value_t& value) const;
+
+    void requirementsMetOrThrow(const std::string& key) const;
+
 protected:
     /**
      * Set the configuration parameter for a given key to
@@ -448,7 +466,6 @@ protected:
 
 private:
     void initialize();
-    struct value_t;
 
     // Access to the configuration variables is protected by the mutex
     mutable std::mutex mutex;
diff --git a/src/configuration_impl.h b/src/configuration_impl.h
new file mode 100644 (file)
index 0000000..6359cc4
--- /dev/null
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ *     Copyright 2017 Couchbase, Inc
+ *
+ *   Licensed 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.
+ */
+
+/*
+ * Contains implementations relating to configuration.h that are not needed in
+ * most places Configuration is. This can be included in a far smaller number
+ * of places, reducing the overhead of including configuration.h.
+ */
+
+#pragma once
+
+#include <boost/variant.hpp>
+
+#include <string>
+#include <vector>
+
+using value_variant_t =
+        boost::variant<size_t, ssize_t, float, bool, std::string>;
+
+class requirements_unsatisfied : public std::logic_error {
+public:
+    requirements_unsatisfied(const std::string& msg) : std::logic_error(msg) {
+    }
+};
+
+class Requirement {
+public:
+    Requirement* add(const std::string& key, value_variant_t value) {
+        requirements.emplace_back(key, value);
+        return this;
+    }
+
+    std::vector<std::pair<std::string, value_variant_t>> requirements;
+};
\ No newline at end of file
index 0d4be30..45b2498 100644 (file)
@@ -217,6 +217,7 @@ static void initialize() {
         << endl
         << "#include \"config.h\"" << endl
         << "#include \"configuration.h\"" << endl
+        << "#include \"configuration_impl.h\"" << endl
         << "#include <platform/sysinfo.h>" << endl;
     validators["range"] = getRangeValidatorCode;
     validators["enum"] = getEnumValidatorCode;
@@ -320,6 +321,78 @@ static string getValidator(const std::string &key, cJSON *o) {
     return (iter->second)(key, o);
 }
 
+/**
+ * Generates code from the requirements field.
+ *
+ * Generates code to be used in generated_configuration.cc constructing the
+ * Requirement object and adding the appropriate requirements.
+ * @param key key to generate requirements for
+ * @param o json object representing the config parameter
+ * @param params json object of all parameters, required to determine the
+ * intended type of the required parameter.
+ * @return string of the code constructing a Requirement object.
+ */
+static string getRequirements(const std::string& key, cJSON* o, cJSON* params) {
+    if (o == NULL) {
+        return "";
+    }
+
+    cJSON* requirements = cJSON_GetObjectItem(o, "requires");
+
+    if (requirements == NULL) {
+        return "";
+    }
+
+    ssize_t num = cJSON_GetArraySize(requirements);
+    if (num <= 0) {
+        return "";
+    }
+
+    std::ostringstream ss;
+
+    ss << "(new Requirement)\n";
+
+    for (int ii = 0; ii < num; ++ii) {
+        cJSON* req = cJSON_GetArrayItem(requirements, ii);
+        char* req_key = req->string;
+
+        cJSON* req_param = cJSON_GetObjectItem(params, req_key);
+
+        if (req_param == NULL) {
+            cerr << "Required parameter \"" << req_key << "\" for parameter \""
+                 << key << "\" does not exist" << endl;
+            exit(1);
+        }
+
+        string type = getDatatype(req_key, req_param);
+        string value;
+
+        switch (req->type) {
+        case cJSON_String:
+            value = std::string("\"") + req->valuestring + "\"";
+            break;
+        case cJSON_Number:
+            if (type == "float") {
+                value = std::to_string(req->valuedouble);
+            } else {
+                value = std::to_string(req->valueint);
+            }
+            break;
+        case cJSON_True:
+            value = "true";
+            break;
+        case cJSON_False:
+            value = "false";
+            break;
+        }
+
+        ss << "        ->add(\"" << req_key << "\", (" << type << ")" << value
+           << ")";
+    }
+
+    return ss.str();
+}
+
 static string getGetterPrefix(const string &str) {
     if (str.compare("bool") == 0) {
         return "is";
@@ -348,7 +421,7 @@ static string getCppName(const string &str) {
     return ss.str();
 }
 
-static void generate(cJSON *o) {
+static void generate(cJSON* o, cJSON* params) {
     cb_assert(o != NULL);
 
     string config_name = o->string;
@@ -365,6 +438,7 @@ static void generate(cJSON *o) {
     }
 
     string validator = getValidator(config_name, o);
+    string requirements = getRequirements(config_name, o, params);
 
     // Generate prototypes
     prototypes << "    " << type
@@ -386,6 +460,10 @@ static void generate(cJSON *o) {
         initialization << "    setValueValidator(\"" << config_name
                        << "\", " << validator << ");" << endl;
     }
+    if (!requirements.empty()) {
+        initialization << "    setRequirements(\"" << config_name << "\", "
+                       << requirements << ");" << endl;
+    }
     if (hasAliases(o)) {
         for (std::string alias : getAliases(o)) {
             initialization << "    addAlias(\"" << config_name << "\", \""
@@ -450,7 +528,7 @@ int main(int argc, char **argv) {
 
     int num = cJSON_GetArraySize(params);
     for (int ii = 0; ii < num; ++ii) {
-        generate(cJSON_GetArrayItem(params, ii));
+        generate(cJSON_GetArrayItem(params, ii), params);
     }
     prototypes << "#endif  // SRC_GENERATED_CONFIGURATION_H_" << endl;