summaryrefslogtreecommitdiffstats
path: root/libnetdata/dyn_conf/tests
diff options
context:
space:
mode:
Diffstat (limited to 'libnetdata/dyn_conf/tests')
-rw-r--r--libnetdata/dyn_conf/tests/sample_test_config.json22
-rw-r--r--libnetdata/dyn_conf/tests/sub_tests/test_parent_child.rb192
-rwxr-xr-xlibnetdata/dyn_conf/tests/test_dyncfg.rb266
-rwxr-xr-xlibnetdata/dyn_conf/tests/test_plugin/test.plugin250
4 files changed, 730 insertions, 0 deletions
diff --git a/libnetdata/dyn_conf/tests/sample_test_config.json b/libnetdata/dyn_conf/tests/sample_test_config.json
new file mode 100644
index 000000000..a6595f124
--- /dev/null
+++ b/libnetdata/dyn_conf/tests/sample_test_config.json
@@ -0,0 +1,22 @@
+{
+ "http_endpoints": {
+ "parent": {
+ "host": "127.0.0.1",
+ "mguid": null,
+ "port": 20001,
+ "ssl": false
+ },
+ "child": {
+ "host": "127.0.0.1",
+ "mguid": "3bc2f7de-1445-11ee-9ed7-3c7c3f21784c",
+ "port": 19999,
+ "ssl": false
+ }
+ },
+ "global": {
+ "test_plugin_name": "external_plugin",
+ "test_array_module_name": "module_of_the_future",
+ "test_single_module_name": "module_of_the_future_single_type",
+ "test_job_name": "fixed_job"
+ }
+}
diff --git a/libnetdata/dyn_conf/tests/sub_tests/test_parent_child.rb b/libnetdata/dyn_conf/tests/sub_tests/test_parent_child.rb
new file mode 100644
index 000000000..820db77f8
--- /dev/null
+++ b/libnetdata/dyn_conf/tests/sub_tests/test_parent_child.rb
@@ -0,0 +1,192 @@
+class ParentChildTest
+ @@plugin_cfg = <<~HEREDOC
+{ "test" : "true" }
+HEREDOC
+ @@plugin_cfg2 = <<~HEREDOC
+{ "asdfgh" : "asdfgh" }
+HEREDOC
+
+ @@job_cfg = <<~HEREDOC
+{ "i am newly created job" : "true" }
+HEREDOC
+
+ def initialize
+ @parent = $config[:http_endpoints][:parent]
+ @child = $config[:http_endpoints][:child]
+ @plugin = $config[:global][:test_plugin_name]
+ @arry_mod = $config[:global][:test_array_module_name]
+ @single_mod = $config[:global][:test_single_module_name]
+ @test_job = $config[:global][:test_job_name]
+ end
+ def check_test_plugin_modules_list(host, child = nil)
+ rc = DynCfgHttpClient.get_plugin_module_list(host, @plugin, child)
+ assert_eq(rc.code, 200, "as HTTP code for get_module_list request on plugin \"#{@plugin}\"")
+ modules = nil
+ assert_nothing_raised do
+ modules = JSON.parse(rc.parsed_response, symbolize_names: true)
+ end
+ assert_has_key?(modules, :modules)
+ assert_eq(modules[:modules].count, 2, "as number of modules in plugin \"#{@plugin}\"")
+ modules[:modules].each do |m|
+ assert_has_key?(m, :name)
+ assert_has_key?(m, :type)
+ assert_is_one_of(m[:type], "job_array", "single")
+ end
+ assert_eq_str(modules[:modules][0][:name], @arry_mod, "name of first module in plugin \"#{@plugin}\"")
+ assert_eq_str(modules[:modules][1][:name], @single_mod, "name of second module in plugin \"#{@plugin}\"")
+ end
+ def run
+ TEST_SUITE("Parent/Child plugin config")
+
+ TEST("parent/child/get_plugin_list", "Get child (hops:1) plugin list trough parent")
+ plugins = DynCfgHttpClient.get_plugin_list(@parent, @child)
+ assert_eq(plugins.code, 200, "as HTTP code for get_plugin_list request")
+ assert_nothing_raised do
+ plugins = JSON.parse(plugins.parsed_response, symbolize_names: true)
+ end
+ assert_has_key?(plugins, :configurable_plugins)
+ assert_array_include?(plugins[:configurable_plugins], @plugin)
+ PASS()
+
+ TEST("parent/child/(set/get)plugin_config", "Set then get and compare child (hops:1) plugin config trough parent")
+ rc = DynCfgHttpClient.set_plugin_config(@parent, @plugin, @@plugin_cfg, @child)
+ assert_eq(rc.code, 200, "as HTTP code for set_plugin_config request")
+
+ rc = DynCfgHttpClient.get_plugin_config(@parent, @plugin, @child)
+ assert_eq(rc.code, 200, "as HTTP code for get_plugin_config request")
+ assert_eq_str(rc.parsed_response.chomp!, @@plugin_cfg, "as plugin config")
+
+ # We do this twice with different configs to ensure first config was not loaded from persistent storage (from previous tests)
+ rc = DynCfgHttpClient.set_plugin_config(@parent, @plugin, @@plugin_cfg2, @child)
+ assert_eq(rc.code, 200, "as HTTP code for set_plugin_config request 2")
+
+ rc = DynCfgHttpClient.get_plugin_config(@parent, @plugin, @child)
+ assert_eq(rc.code, 200, "as HTTP code for get_plugin_config request 2")
+ assert_eq_str(rc.parsed_response.chomp!, @@plugin_cfg2, "set/get plugin config 2")
+ PASS()
+
+ TEST("child/get_plugin_config", "Get child (hops:0) plugin config and compare with what we got trough parent (set_plugin_config from previous test)")
+ rc = DynCfgHttpClient.get_plugin_config(@child, @plugin, nil)
+ assert_eq(rc.code, 200, "as HTTP code for get_plugin_config request")
+ assert_eq_str(rc.parsed_response.chomp!, @@plugin_cfg2.chomp, "as plugin config")
+ PASS()
+
+ TEST("parent/child/plugin_module_list", "Get child (hops:1) plugin module list trough parent and check its contents")
+ check_test_plugin_modules_list(@parent, @child)
+ PASS()
+
+ TEST("child/plugin_module_list", "Get child (hops:0) plugin module list directly and check its contents")
+ check_test_plugin_modules_list(@child, nil)
+ PASS()
+
+ TEST("parent/child/module/jobs", "Get list of jobs from child (hops:1) trough parent and check its contents, check job updates")
+ rc = DynCfgHttpClient.get_job_list(@parent, @plugin, @arry_mod, @child)
+ assert_eq(rc.code, 200, "as HTTP code for get_jobs request")
+ jobs = nil
+ assert_nothing_raised do
+ jobs = JSON.parse(rc.parsed_response, symbolize_names: true)
+ end
+ assert_has_key?(jobs, :jobs)
+ new_job = jobs[:jobs].find {|i| i[:name] == @test_job}
+ assert_not_nil(new_job)
+ assert_has_key?(new_job, :status)
+ assert_not_eq_str(new_job[:status], "unknown", "job status is other than unknown")
+ assert_has_key?(new_job, :flags)
+ assert_array_include?(new_job[:flags], "JOB_FLG_STREAMING_PUSHED")
+ PASS()
+
+ TEST("child/module/jobs", "Get list of jobs direct from child (hops:0) and check its contents, check job updates")
+ rc = DynCfgHttpClient.get_job_list(@child, @plugin, @arry_mod, nil)
+ assert_eq(rc.code, 200, "as HTTP code for get_jobs request")
+ jobs = nil
+ assert_nothing_raised do
+ jobs = JSON.parse(rc.parsed_response, symbolize_names: true)
+ end
+ assert_has_key?(jobs, :jobs)
+ new_job = jobs[:jobs].find {|i| i[:name] == @test_job}
+ assert_not_nil(new_job)
+ assert_has_key?(new_job, :status)
+ assert_not_eq_str(new_job[:status], "unknown", "job status is other than unknown")
+ assert_has_key?(new_job, :flags)
+
+ assert_array_not_include?(new_job[:flags], "JOB_FLG_STREAMING_PUSHED") # this is plugin directly at child so it should not show this flag
+ PASS()
+
+ TEST("parent/child/single_module/jobs", "Attempt getting list of jobs from child (hops:1) trough parent on single module. Check it fails properly")
+ rc = DynCfgHttpClient.get_job_list(@parent, @plugin, @single_mod, @child)
+ assert_eq(rc.code, 400, "as HTTP code for get_jobs request")
+ assert_eq_str(rc.parsed_response, '400 - this module is not array type', "as HTTP code for get_jobs request on single module")
+ PASS()
+
+ created_job = SecureRandom.uuid
+ TEST("parent/child/module/cr_del_job", "Create and delete job on child (hops:1) trough parent")
+ # create new job
+ rc = DynCfgHttpClient.create_job(@parent, @plugin, @arry_mod, created_job, @@job_cfg, @child)
+ assert_eq_http_code(rc, 200, "as HTTP code for create_job request")
+ # check this job is in job list @parent
+ rc = DynCfgHttpClient.get_job_list(@parent, @plugin, @arry_mod, @child)
+ assert_eq(rc.code, 200, "as HTTP code for get_jobs request")
+ jobs = nil
+ assert_nothing_raised do
+ jobs = JSON.parse(rc.parsed_response, symbolize_names: true)
+ end
+ assert_has_key?(jobs, :jobs)
+ new_job = jobs[:jobs].find {|i| i[:name] == created_job}
+ assert_not_nil(new_job)
+ # check this job is in job list @child
+ rc = DynCfgHttpClient.get_job_list(@child, @plugin, @arry_mod, nil)
+ assert_eq(rc.code, 200, "as HTTP code for get_jobs request")
+ jobs = nil
+ assert_nothing_raised do
+ jobs = JSON.parse(rc.parsed_response, symbolize_names: true)
+ end
+ assert_has_key?(jobs, :jobs)
+ new_job = jobs[:jobs].find {|i| i[:name] == created_job}
+ assert_not_nil(new_job)
+ # check we can get job config back
+ rc = DynCfgHttpClient.get_job_config(@parent, @plugin, @arry_mod, created_job, @child)
+ assert_eq(rc.code, 200, "as HTTP code for get_job_config request")
+ assert_eq_str(rc.parsed_response.chomp!, @@job_cfg, "as job config")
+ # delete job
+ rc = DynCfgHttpClient.delete_job(@parent, @plugin, @arry_mod, created_job, @child)
+ assert_eq(rc.code, 200, "as HTTP code for delete_job request")
+ # Check it is not in parents job list anymore
+ rc = DynCfgHttpClient.get_job_list(@parent, @plugin, @arry_mod, @child)
+ assert_eq(rc.code, 200, "as HTTP code for get_jobs request")
+ jobs = nil
+ assert_nothing_raised do
+ jobs = JSON.parse(rc.parsed_response, symbolize_names: true)
+ end
+ assert_has_key?(jobs, :jobs)
+ new_job = jobs[:jobs].find {|i| i[:name] == created_job}
+ assert_nil(new_job)
+ # Check it is not in childs job list anymore
+ rc = DynCfgHttpClient.get_job_list(@child, @plugin, @arry_mod, nil)
+ assert_eq(rc.code, 200, "as HTTP code for get_jobs request")
+ jobs = nil
+ assert_nothing_raised do
+ jobs = JSON.parse(rc.parsed_response, symbolize_names: true)
+ end
+ assert_has_key?(jobs, :jobs)
+ new_job = jobs[:jobs].find {|i| i[:name] == created_job}
+ assert_nil(new_job)
+ PASS()
+
+ TEST("parent/child/module/del_undeletable_job", "Try delete job on child (child rejects), check failure case works (hops:1)")
+ # test if plugin rejects job deletion the job still remains in list as it should
+ rc = DynCfgHttpClient.delete_job(@parent, @plugin, @arry_mod, @test_job, @child)
+ assert_eq(rc.code, 500, "as HTTP code for delete_job request")
+ rc = DynCfgHttpClient.get_job_list(@parent, @plugin, @arry_mod, @child)
+ assert_eq(rc.code, 200, "as HTTP code for get_jobs request")
+ jobs = nil
+ assert_nothing_raised do
+ jobs = JSON.parse(rc.parsed_response, symbolize_names: true)
+ end
+ assert_has_key?(jobs, :jobs)
+ job = jobs[:jobs].find {|i| i[:name] == @test_job}
+ assert_not_nil(job)
+ PASS()
+ end
+end
+
+ParentChildTest.new.run()
diff --git a/libnetdata/dyn_conf/tests/test_dyncfg.rb b/libnetdata/dyn_conf/tests/test_dyncfg.rb
new file mode 100755
index 000000000..1b4b3a068
--- /dev/null
+++ b/libnetdata/dyn_conf/tests/test_dyncfg.rb
@@ -0,0 +1,266 @@
+#!/usr/bin/env ruby
+
+require 'json'
+require 'httparty'
+require 'pastel'
+require 'securerandom'
+
+ARGV.length == 1 or raise "Usage: #{$0} <config file>"
+config_file = ARGV[0]
+
+File.exist?(config_file) or raise "File not found: #{config_file}"
+
+$config = JSON.parse(File.read(config_file), symbolize_names: true)
+
+$plugin_name = $config[:global][:test_plugin_name]
+$pastel = Pastel.new
+
+class TestRunner
+ attr_reader :stats
+ def initialize
+ @stats = {
+ :suites => 0,
+ :tests => 0,
+ :assertions => 0
+ }
+ @test = nil
+ end
+ def add_assertion()
+ @stats[:assertions] += 1
+ end
+ def FAIL(msg, exception = nil, loc = nil)
+ puts $pastel.red.bold(" ✕ FAIL")
+ STDERR.print " "
+ if loc
+ STDERR.print $pastel.yellow("@#{loc.path}:#{loc.lineno}: ")
+ else
+ STDERR.print $pastel.yellow("@#{caller_locations(1, 1).first.path}:#{caller_locations(1, 1).first.lineno}: ")
+ end
+ STDERR.puts msg
+ STDERR.puts exception.full_message(:highlight => true) if exception
+ STDERR.puts $pastel.yellow(" Backtrace:")
+ caller.each do |line|
+ STDERR.puts " #{line}"
+ end
+ exit 1
+ end
+ def PASS()
+ STDERR.puts $pastel.green.bold(" ✓ PASS")
+ @stats[:tests] += 1
+ @test = nil
+ end
+ def TEST_SUITE(name)
+ puts $pastel.bold("• TEST SUITE: \"#{name}\"")
+ @stats[:suites] += 1
+ end
+ def assert_no_test_running()
+ unless @test.nil?
+ STDERR.puts $pastel.red("\nFATAL: Test \"#{@test}\" did not call PASS() or FAIL()!")
+ exit 1
+ end
+ end
+ def TEST(name, description = nil)
+ assert_no_test_running()
+ @test = name
+ col = 0
+ txt = " ├─ T: #{name} "
+ col += txt.length
+ print $pastel.bold(txt)
+
+ tab = 50
+ rem = tab - (col % tab)
+ rem.times do putc ' ' end
+ col += rem
+
+ if (description)
+ txt = " - #{description} "
+ col += txt.length
+ print txt
+
+ tab = 180
+ rem = tab - (col % tab)
+ rem.times do putc '.' end
+ end
+ end
+ def FINALIZE()
+ assert_no_test_running()
+ end
+end
+
+$test_runner = TestRunner.new
+def FAIL(msg, exception = nil, loc = nil)
+ $test_runner.FAIL(msg, exception, loc)
+end
+def PASS()
+ $test_runner.PASS()
+end
+def TEST_SUITE(name)
+ $test_runner.TEST_SUITE(name)
+end
+def TEST(name, description = nil)
+ $test_runner.TEST(name, description)
+end
+
+def assert_eq(got, expected, msg = nil)
+ unless got == expected
+ FAIL("Expected #{expected}, got #{got} #{msg ? "(#{msg})" : ""}", nil, caller_locations(1, 1).first)
+ end
+ $test_runner.add_assertion()
+end
+def assert_eq_http_code(got, expected, msg = nil)
+ unless got.code == expected
+ FAIL("Expected #{expected}, got #{got}. Server \"#{got.parsed_response}\" #{msg ? "(#{msg})" : ""}", nil, caller_locations(1, 1).first)
+ end
+ $test_runner.add_assertion()
+end
+def assert_eq_str(got, expected, msg = nil)
+ unless got == expected
+ FAIL("Strings do not match #{msg ? "(#{msg})" : ""}", nil, caller_locations(1, 1).first)
+ end
+ $test_runner.add_assertion()
+end
+def assert_not_eq_str(got, expected, msg = nil)
+ unless got != expected
+ FAIL("Strings shoud not match #{msg ? "(#{msg})" : ""}", nil, caller_locations(1, 1).first)
+ end
+ $test_runner.add_assertion()
+end
+def assert_nothing_raised()
+ begin
+ yield
+ rescue Exception => e
+ FAIL("Unexpected exception of type #{e.class} raised. Msg: \"#{e.message}\"", e, caller_locations(1, 1).first)
+ end
+ $test_runner.add_assertion()
+end
+def assert_has_key?(hash, key)
+ unless hash.has_key?(key)
+ FAIL("Expected key \"#{key}\" in hash", nil, caller_locations(1, 1).first)
+ end
+ $test_runner.add_assertion()
+end
+def assert_array_include?(array, value)
+ unless array.include?(value)
+ FAIL("Expected array to include \"#{value}\"", nil, caller_locations(1, 1).first)
+ end
+ $test_runner.add_assertion()
+end
+def assert_array_not_include?(array, value)
+ if array.include?(value)
+ FAIL("Expected array to not include \"#{value}\"", nil, caller_locations(1, 1).first)
+ end
+ $test_runner.add_assertion()
+end
+def assert_is_one_of(value, *values)
+ unless values.include?(value)
+ FAIL("Expected value to be one of #{values.join(", ")}", nil, caller_locations(1, 1).first)
+ end
+ $test_runner.add_assertion()
+end
+def assert_not_nil(value)
+ if value.nil?
+ FAIL("Expected value to not be nil", nil, caller_locations(1, 1).first)
+ end
+ $test_runner.add_assertion()
+end
+def assert_nil(value)
+ unless value.nil?
+ FAIL("Expected value to be nil", nil, caller_locations(1, 1).first)
+ end
+ $test_runner.add_assertion()
+end
+
+
+class DynCfgHttpClient
+ def self.protocol(cfg)
+ return cfg[:ssl] ? 'https://' : 'http://'
+ end
+ def self.url_base(host)
+ return "#{protocol(host)}#{host[:host]}:#{host[:port]}"
+ end
+ def self.get_url_cfg_base(host, child = nil)
+ url = url_base(host)
+ url += "/host/#{child[:mguid]}" if child
+ url += "/api/v2/config"
+ return url
+ end
+ def self.get_url_cfg_plugin(host, plugin, child = nil)
+ return get_url_cfg_base(host, child) + '/' + plugin
+ end
+ def self.get_url_cfg_module(host, plugin, mod, child = nil)
+ return get_url_cfg_plugin(host, plugin, child) + '/' + mod
+ end
+ def self.get_url_cfg_job(host, plugin, mod, job_id, child = nil)
+ return get_url_cfg_module(host, plugin, mod, child) + "/#{job_id}"
+ end
+ def self.get_plugin_list(host, child = nil)
+ begin
+ return HTTParty.get(get_url_cfg_base(host, child), verify: false, format: :plain)
+ rescue => e
+ FAIL(e.message, e)
+ end
+ end
+ def self.get_plugin_config(host, plugin, child = nil)
+ begin
+ return HTTParty.get(get_url_cfg_plugin(host, plugin, child), verify: false)
+ rescue => e
+ FAIL(e.message, e)
+ end
+ end
+ def self.set_plugin_config(host, plugin, cfg, child = nil)
+ begin
+ return HTTParty.put(get_url_cfg_plugin(host, plugin, child), verify: false, body: cfg)
+ rescue => e
+ FAIL(e.message, e)
+ end
+ end
+ def self.get_plugin_module_list(host, plugin, child = nil)
+ begin
+ return HTTParty.get(get_url_cfg_plugin(host, plugin, child) + "/modules", verify: false, format: :plain)
+ rescue => e
+ FAIL(e.message, e)
+ end
+ end
+ def self.get_job_list(host, plugin, mod, child = nil)
+ begin
+ return HTTParty.get(get_url_cfg_module(host, plugin, mod, child) + "/jobs", verify: false, format: :plain)
+ rescue => e
+ FAIL(e.message, e)
+ end
+ end
+ def self.create_job(host, plugin, mod, job_id, job_cfg, child = nil)
+ begin
+ return HTTParty.post(get_url_cfg_job(host, plugin, mod, job_id, child), verify: false, body: job_cfg)
+ rescue => e
+ FAIL(e.message, e)
+ end
+ end
+ def self.delete_job(host, plugin, mod, job_id, child = nil)
+ begin
+ return HTTParty.delete(get_url_cfg_job(host, plugin, mod, job_id, child), verify: false)
+ rescue => e
+ FAIL(e.message, e)
+ end
+ end
+ def self.get_job_config(host, plugin, mod, job_id, child = nil)
+ begin
+ return HTTParty.get(get_url_cfg_job(host, plugin, mod, job_id, child), verify: false, format: :plain)
+ rescue => e
+ FAIL(e.message, e)
+ end
+ end
+ def self.set_job_config(host, plugin, mod, job_id, job_cfg, child = nil)
+ begin
+ return HTTParty.put(get_url_cfg_job(host, plugin, mod, job_id, child), verify: false, body: job_cfg)
+ rescue => e
+ FAIL(e.message, e)
+ end
+ end
+end
+
+require_relative 'sub_tests/test_parent_child.rb'
+
+$test_runner.FINALIZE()
+puts $pastel.green.bold("All tests passed!")
+puts ("Total #{$test_runner.stats[:assertions]} assertions, #{$test_runner.stats[:tests]} tests in #{$test_runner.stats[:suites]} suites")
+exit 0
diff --git a/libnetdata/dyn_conf/tests/test_plugin/test.plugin b/libnetdata/dyn_conf/tests/test_plugin/test.plugin
new file mode 100755
index 000000000..b890ab314
--- /dev/null
+++ b/libnetdata/dyn_conf/tests/test_plugin/test.plugin
@@ -0,0 +1,250 @@
+#!/usr/bin/env ruby
+
+# bogus chart that we create just so there is at least one chart
+CHART_TYPE = 'lines'
+UPDATE_EVERY = 1
+PRIORITY = 100000
+CHART_NAME = 'number_of_processes'
+DIMENSION_NAME = 'running'
+
+$plugin_name = "external_plugin"
+$plugin_version = "0.0.1"
+$plugin_config = <<-HEREDOC
+test_plugin_config
+hableba hableba hableba
+HEREDOC
+
+$array_module_name = 'module_of_the_future'
+$fixed_job_name = 'fixed_job'
+
+$modules = {
+ $array_module_name => {
+ :type => :job_array,
+ :jobs => {
+ $fixed_job_name => {
+ :type => :fixed,
+ :config => <<-HEREDOC
+fixed_job_config
+HEREDOC
+ },
+ },
+ :config => <<-HEREDOC
+module_of_the_future_config
+HEREDOC
+ },
+ "module_of_the_future_single_type" => {
+ :type => :single,
+ :jobs => {},
+ :config => <<-HEREDOC
+module_of_the_future_single_type_config
+HEREDOC
+ }
+}
+
+def out(str)
+ $log.puts "2 NETDATA> #{str}"
+ $stdout.puts str
+ $stdout.flush
+ $log.flush
+end
+
+def log(str)
+ $log.puts "LOG > #{str}"
+ $log.flush
+end
+
+#TODO this is AI code, verify
+def split_with_quotes(str)
+ result = []
+ current_word = ""
+ in_quotes = false
+ escaped = false
+
+ str.each_char do |char|
+ if char == '\\' && !escaped
+ escaped = true
+ next
+ end
+
+ if char == '"' && !escaped
+ in_quotes = !in_quotes
+ current_word << char
+ elsif char == ' ' && !in_quotes
+ result << current_word unless current_word.empty?
+ current_word = ""
+ else
+ current_word << char
+ end
+
+ escaped = false
+ end
+
+ result << current_word unless current_word.empty?
+
+ result
+end
+
+
+def print_startup_messages
+ out "DYNCFG_ENABLE #{$plugin_name}"
+ $modules.each do |name, module_config|
+ out "DYNCFG_REGISTER_MODULE #{name} #{module_config[:type]}"
+ end
+ out "CHART system.#{CHART_NAME} '' 'Number of running processes' 'processes' processes processes.#{CHART_NAME} #{CHART_TYPE} #{PRIORITY} #{UPDATE_EVERY}"
+ out "DIMENSION #{DIMENSION_NAME} '' absolute 1 1"
+
+ $modules.each do |mod_name, mod|
+ next unless mod[:type] == :job_array
+ mod[:jobs].each do |job_name, job|
+ next unless job[:type] == :fixed
+ out "DYNCFG_REGISTER_JOB #{mod_name} #{job_name} stock 0"
+ out "REPORT_JOB_STATUS #{$array_module_name} #{$fixed_job_name} running 0"
+ end
+ end
+end
+
+def function_result(txid, msg, result)
+ out "FUNCTION_RESULT_BEGIN #{txid} #{result} text/plain 5"
+ out msg
+ out "FUNCTION_RESULT_END"
+end
+
+def process_payload_function(params)
+ log "payload function #{params[:fncname]}, #{params[:fncparams]}"
+ fnc_name, mod_name, job_name = params[:fncparams]
+ case fnc_name
+ when 'set_plugin_config'
+ $plugin_config = params[:payload]
+ function_result(params[:txid], "plugin config set", 1)
+ when 'set_module_config'
+ mod = $modules[mod_name]
+ return function_result(params[:txid], "no such module", 0) if mod.nil?
+ mod[:config] = params[:payload]
+ function_result(params[:txid], "module config set", 1)
+ when 'set_job_config'
+ mod = $modules[mod_name]
+ return function_result(params[:txid], "no such module", 0) if mod.nil?
+ job = mod[:jobs][job_name]
+ if job.nil?
+ job = Hash.new if job.nil?
+ job[:type] = :dynamic
+ mod[:jobs][job_name] = job
+ end
+ job[:config] = params[:payload]
+ function_result(params[:txid], "job config set", 1)
+ end
+end
+
+def process_function(params)
+ log "normal function #{params[:fncname]}, #{params[:fncparams]}"
+ fnc_name, mod_name, job_name = params[:fncparams]
+ case fnc_name
+ when 'get_plugin_config'
+ function_result(params[:txid], $plugin_config, 1)
+ when 'get_module_config'
+ return function_result(params[:txid], "no such module", 0) unless $modules.has_key?(mod_name)
+ function_result(params[:txid], $modules[mod_name][:config], 1)
+ when 'get_job_config'
+ mod = $modules[mod_name]
+ return function_result(params[:txid], "no such module", 0) if mod.nil?
+ job = mod[:jobs][job_name]
+ return function_result(params[:txid], "no such job", 0) if job.nil?
+ function_result(params[:txid], job[:config], 1)
+ when 'delete_job'
+ mod = $modules[mod_name]
+ return function_result(params[:txid], "no such module", 0) if mod.nil?
+ job = mod[:jobs][job_name]
+ return function_result(params[:txid], "no such job", 0) if job.nil?
+ if job[:type] == :fixed
+ return function_result(params[:txid], "this job can't be deleted", 0)
+ else
+ mod[:jobs].delete(job_name)
+ function_result(params[:txid], "job deleted", 1)
+ end
+ end
+end
+
+$inflight_incoming = nil
+def process_input(input)
+ words = split_with_quotes(input)
+
+ unless $inflight_incoming.nil?
+ if input == "FUNCTION_PAYLOAD_END"
+ log $inflight_incoming[:payload]
+ process_payload_function($inflight_incoming)
+ $inflight_incoming = nil
+ else
+ $inflight_incoming[:payload] << input
+ $inflight_incoming[:payload] << "\n"
+ end
+ return
+ end
+
+ case words[0]
+ when "FUNCTION", "FUNCTION_PAYLOAD"
+ params = {}
+ params[:command] = words[0]
+ params[:txid] = words[1]
+ params[:timeout] = words[2].to_i
+ params[:fncname] = words[3]
+ params[:fncname] = params[:fncname][1..-2] if params[:fncname].start_with?('"') && params[:fncname].end_with?('"')
+ if params[:command] == "FUNCTION_PAYLOAD"
+ $inflight_incoming = Hash.new
+ params[:fncparams] = split_with_quotes(params[:fncname])
+ params[:fncname] = params[:fncparams][0]
+ $inflight_incoming[:txid] = params[:txid]
+ $inflight_incoming[:fncname] = params[:fncname]
+ $inflight_incoming[:params] = params
+ $inflight_incoming[:fncparams] = params[:fncparams]
+ $inflight_incoming[:payload] = ""
+ else
+ params[:fncparams] = split_with_quotes(params[:fncname])
+ params[:fncname] = params[:fncparams][0]
+ process_function(params)
+ end
+ end
+end
+
+def read_and_output_metric
+ processes = `ps -e | wc -l`.to_i - 1 # -1 to exclude the header line
+ timestamp = Time.now.to_i
+
+ puts "BEGIN system.#{CHART_NAME}"
+ puts "SET #{DIMENSION_NAME} = #{processes}"
+ puts "END"
+end
+
+def the_main
+ $stderr.reopen("/tmp/test_plugin_err.log", "w")
+ $log = File.open("/tmp/test_plugin.log", "w")
+ $log.puts "Starting plugin"
+ print_startup_messages
+ $log.puts "init done"
+ $log.flush
+
+ last_metric_time = Time.now
+
+ loop do
+ time_since_last_metric = Time.now - last_metric_time
+
+ # If it's been more than 1 second since we collected metrics, collect them now
+ if time_since_last_metric >= 1
+ read_and_output_metric
+ last_metric_time = Time.now
+ end
+
+ # Use select to wait for input, but only wait up to the time remaining until we need to collect metrics again
+ remaining_time = [1 - time_since_last_metric, 0].max
+ if select([$stdin], nil, nil, remaining_time)
+ input = $stdin.gets
+ next if input.class != String
+ input.chomp!
+ $log.puts "RAW INPUT< #{input}"
+ $log.flush
+ process_input(input)
+ end
+ end
+end
+
+
+the_main if __FILE__ == $PROGRAM_NAME