Merge pull request #13 from apache/allow-for-dynamic-ioq-classes

Allow for dynamic ioq classes
diff --git a/IOQ2.md b/IOQ2.md
index ddf8d22..849fbe2 100644
--- a/IOQ2.md
+++ b/IOQ2.md
@@ -611,6 +611,16 @@
 ```
 
 
+### Dynamic IOQ classes
+
+To support 3rd party IO channels for things like search/geo/cache/etc, you can
+manually set a config priority for the desired class, and then it will be
+picked up by `ioq_config:is_valid_class/1`. Because the `ioq_config:set_*`
+setters depend on `is_valid_class`, you must manually define the priority
+initially, for example with `config:set("ioq.classes", "search", "1.0").`.
+Afterwards, you'll be able to utilize the setters as expected.
+
+
 ## ioq_server2:get_state
 
 You can see a human readable representation of the IOQ2 server state with the
diff --git a/include/ioq.hrl b/include/ioq.hrl
index 55c95f8..499a140 100644
--- a/include/ioq.hrl
+++ b/include/ioq.hrl
@@ -19,6 +19,15 @@
 -define(DISPATCH_SINGLE_SERVER, "single_server").
 -define(DISPATCH_SERVER_PER_SCHEDULER, "server_per_scheduler").
 
+%% Config Categories
+-define(SHARD_CLASS_SEPARATOR, "||").
+-define(IOQ2_CONFIG, "ioq2").
+-define(IOQ2_BYPASS_CONFIG, "ioq2.bypass").
+-define(IOQ2_SHARDS_CONFIG, "ioq2.shards").
+-define(IOQ2_USERS_CONFIG, "ioq2.users").
+-define(IOQ2_CLASSES_CONFIG, "ioq2.classes").
+
+
 -define(DEFAULT_CLASS_PRIORITIES, [
     {customer, 1.0},
     {internal_repl, 0.001},
diff --git a/src/ioq_config.erl b/src/ioq_config.erl
index 9cc2371..6b324e3 100644
--- a/src/ioq_config.erl
+++ b/src/ioq_config.erl
@@ -54,14 +54,6 @@
 ]).
 
 
--define(SHARD_CLASS_SEPARATOR, "||").
--define(IOQ2_CONFIG, "ioq2").
--define(IOQ2_BYPASS_CONFIG, "ioq2.bypass").
--define(IOQ2_SHARDS_CONFIG, "ioq2.shards").
--define(IOQ2_USERS_CONFIG, "ioq2.users").
--define(IOQ2_CLASSES_CONFIG, "ioq2.classes").
-
-
 ioq_classes() ->
     [Class || {Class, _Priority} <- ?DEFAULT_CLASS_PRIORITIES].
 
@@ -143,8 +135,19 @@
     ok = set_config(?IOQ2_USERS_CONFIG, User, Value, Reason).
 
 
-is_valid_class(Class) ->
-    lists:member(Class, ioq_classes()).
+is_valid_class(Class) when is_atom(Class) ->
+    case lists:member(Class, ioq_classes()) of
+        true ->
+            true;
+        false ->
+            SClass = atom_to_list(Class),
+            case config:get(?IOQ2_CLASSES_CONFIG, SClass, undefined) of
+                undefined ->
+                    false;
+                _ ->
+                    true
+            end
+    end.
 
 
 check_float_value(Value) when is_float(Value) ->
diff --git a/test/ioq_config_tests.erl b/test/ioq_config_tests.erl
index a7fd491..2b6e153 100644
--- a/test/ioq_config_tests.erl
+++ b/test/ioq_config_tests.erl
@@ -260,3 +260,34 @@
     ok = ioq_config:set_bypass(interactive, true, "Bypassing interactive"),
     Value = config:get_boolean("ioq2.bypass", "interactive", false),
     ?_assertEqual(true, Value).
+
+
+valid_classes_test_() ->
+    {
+        "Test ioq_config is_valid_class logic",
+        {
+            foreach,
+            fun() -> test_util:start_applications([config, couch_log]) end,
+            fun(_) -> test_util:stop_applications([config, couch_log]) end,
+            [
+                fun check_default_classes/1,
+                fun check_undeclared_class/1,
+                fun check_declared_class/1
+            ]
+        }
+    }.
+
+
+check_default_classes(_) ->
+    Classes = [C || {C, _P} <- ?DEFAULT_CLASS_PRIORITIES],
+    [?_assert(ioq_config:is_valid_class(C)) || C <- Classes].
+
+
+check_undeclared_class(_) ->
+    ?_assert(not ioq_config:is_valid_class(search)).
+
+
+check_declared_class(_) ->
+    config:set(?IOQ2_CLASSES_CONFIG, "search", "1.0", false),
+    ?_assert(ioq_config:is_valid_class(search)).
+