glowworm 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +6 -0
  3. data/.yardopts +1 -0
  4. data/Gemfile +5 -0
  5. data/Gemfile.lock +129 -0
  6. data/LICENSE +19 -0
  7. data/README.md +326 -0
  8. data/Rakefile +29 -0
  9. data/bin/basic_server_tester +311 -0
  10. data/bin/em_server/config.ru +2 -0
  11. data/bin/em_server/em_server.rb +19 -0
  12. data/bin/em_server_tester +68 -0
  13. data/bin/glowworm +90 -0
  14. data/bin/load_tester +84 -0
  15. data/ci_jobs/glowworm-continuous-deploy-next-staging/run.sh +10 -0
  16. data/ci_jobs/glowworm-integrations/run.sh +15 -0
  17. data/ci_jobs/glowworm-performance/run.sh +2 -0
  18. data/ci_jobs/glowworm-robustness/run.sh +2 -0
  19. data/ci_jobs/glowworm-units/run.sh +13 -0
  20. data/ci_jobs/setup.sh +119 -0
  21. data/example/example_server.ecology +6 -0
  22. data/example/example_server.rb +32 -0
  23. data/glowworm.gemspec +54 -0
  24. data/lib/glowworm.rb +501 -0
  25. data/lib/glowworm/em.rb +8 -0
  26. data/lib/glowworm/no_bg.rb +2 -0
  27. data/lib/glowworm/version.rb +3 -0
  28. data/server/Gemfile +27 -0
  29. data/server/Gemfile.lock +87 -0
  30. data/server/PROTOCOL +39 -0
  31. data/server/check_mk_checks/check_glowworm_server +43 -0
  32. data/server/db_migrations/20111004214649_change_feature_accounts_to_string.rb +60 -0
  33. data/server/db_migrations/20111028104546_add_value_to_account_set_features.rb +12 -0
  34. data/server/db_migrations/20120217090636_add_fully_active_flag_to_features.rb +15 -0
  35. data/server/example_test_data.rb +66 -0
  36. data/server/glowworm_server.ecology.erb +16 -0
  37. data/server/glowworm_server.rb +226 -0
  38. data/server/run/server.sh +7 -0
  39. data/server/server_test.rb +72 -0
  40. data/server/version.rb +3 -0
  41. data/test/integration/basic_server_test.rb +90 -0
  42. data/test/integration/create_sqlite_data.rb +196 -0
  43. data/test/integration/em_server_test.rb +68 -0
  44. data/test/integration/gemfile_for_specific_glowworm_version +17 -0
  45. data/test/integration/gemfile_for_specific_glowworm_version.lock +55 -0
  46. data/test/integration/integration_test_helper.rb +153 -0
  47. data/test/integration/load_test.rb +59 -0
  48. data/test/integration/nginx.conf +23 -0
  49. data/test/integration/server_test.ecology.erb +6 -0
  50. data/test/test_helper.rb +47 -0
  51. data/test/units/em_test.rb +41 -0
  52. data/test/units/feature_flag_test.rb +297 -0
  53. data/test/units/no_bg_test.rb +40 -0
  54. data/test/units/request_test.rb +51 -0
  55. metadata +410 -0
@@ -0,0 +1,7 @@
1
+ #!/bin/bash -e
2
+
3
+ APP_ROOT=/opt/ooyala/glowworm/current
4
+
5
+ cd $APP_ROOT && \
6
+ source $APP_ROOT/config/pushed_environment.rb && \
7
+ /opt/ruby/1.9/bin/bundle exec /opt/ruby/1.9/bin/ruby glowworm_server.rb 2>&1 |cronolog --symlink=$SHARED_PATH/log/current $SHARED_PATH/log/glowworm.log.%Y-%m-%d
@@ -0,0 +1,72 @@
1
+ require "rubygems"
2
+ require "bundler"
3
+ Bundler.require(:default, :development)
4
+ require "minitest/autorun"
5
+
6
+ # For testing Glowworm itself, use the local version *first*.
7
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), "..", "lib")
8
+
9
+ require "glowworm"
10
+
11
+ class Scope::TestCase
12
+ def set_up_ecology(file_contents, filename = "some.ecology")
13
+ ENV["ECOLOGY_SPEC"] = filename
14
+ File.expects(:exist?).with(filename).returns(true)
15
+ File.expects(:read).with(filename).returns(file_contents)
16
+ end
17
+ end
18
+
19
+ class GlowwormServerTest < Scope::TestCase
20
+ def mock_sequel_query(mock_db, name, data)
21
+ data_stub = stub(:all => data) do
22
+ stubs(:select).returns(self)
23
+ stubs(:filter).returns(self)
24
+ stubs(:join).returns(self)
25
+ stubs(:right_join).returns(self)
26
+ end
27
+ mock_db.stubs(:[]).with(name).returns(data_stub)
28
+
29
+ # Yield each line of data in turn when somebody calls .each
30
+ data[1..-1].inject(mock_db.stubs(:each).yields(data[0])) do |expectation, data_item|
31
+ expectation.then.yields(data_item)
32
+ end
33
+ end
34
+
35
+ setup do
36
+ set_up_ecology <<JSON
37
+ {
38
+ "application": "Glowworm Server Test",
39
+ "features": {
40
+ }
41
+ }
42
+ JSON
43
+ end
44
+
45
+ context "with mocked test data" do
46
+ setup do
47
+ @mock_db = mock("Sequel Database object")
48
+ Sequel.expects(:connect).returns(@mock_db)
49
+ @logger_mock = mock("Termite logger")
50
+ @logger_mock.stubs(:warn => nil, :info => nil, :debug => nil)
51
+ Termite::Logger.expects(:new).returns(@logger_mock)
52
+ end
53
+
54
+ should "Return results for all accounts and features" do
55
+ mock_sequel_query(@mock_db, :account_set_accounts,
56
+ [
57
+ {:account => "123", :account_set_id => 1},
58
+ {:account => "456", :account_set_id => 2},
59
+ ])
60
+ mock_sequel_query(@mock_db, :account_set_features,
61
+ [
62
+ {:name => "foo_feature", :account_set_id => 1}
63
+ ])
64
+ mock_sequel_query(@mock_db, :feature_account_overrides,
65
+ [
66
+ {:name => "foo_feature", :account => "456", :value => true}
67
+ ])
68
+
69
+ assert true
70
+ end
71
+ end
72
+ end
data/server/version.rb ADDED
@@ -0,0 +1,3 @@
1
+ module Glowworm
2
+ VERSION = "0.3.0"
3
+ end
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.join(File.dirname(__FILE__), "integration_test_helper")
4
+ require "net/http"
5
+ require 'multi_json'
6
+ require "trollop"
7
+ require "psych"
8
+
9
+ BASIC_SERVER_TESTER_BIN="#{GLOWWORM_ROOT}/bin/basic_server_tester"
10
+
11
+ class BasicServerTest < Scope::TestCase
12
+ setup_once do
13
+ system("ECOLOGY_SPEC=\"#{GLOWWORM_ECOLOGY_FILE}\" #{GLOWWORM_ROOT}/test/integration/create_sqlite_data.rb")
14
+ fail "Couldn't create SQLite data successfully!" unless $?.success?
15
+ end
16
+
17
+ context "with Glowworm server and tester" do
18
+ setup_once do
19
+ tester_tapy = results_path('basic_server_test.yaml')
20
+
21
+ # The tester command outputs results via TAP-Y and diagnostics to STDERR
22
+ tester_cmd = cmd_in_client_ruby_dist(BASIC_SERVER_TESTER_BIN, "--server #{GW_URI} | tee #{tester_tapy}")
23
+ @topology = start_tester_topology(tester_cmd)
24
+
25
+ STDERR.puts "tester status #{@topology[:gw_tester].status}"
26
+
27
+ @tester_results = Psych.parse_stream(IO.read(tester_tapy)).to_ruby
28
+ end
29
+
30
+ teardown_once do
31
+ @topology.stop_all
32
+
33
+ # TODO(noah): Hideous hack until we have real RVM/Nodule integration
34
+ system("pkill -9 -f glowworm_server.rb")
35
+ end
36
+
37
+ should "test server response" do
38
+ contained_summary = false
39
+ @tester_results.each do |tap_entry|
40
+ if "final" == tap_entry["type"]
41
+ contained_summary = true
42
+ counts = tap_entry["counts"]
43
+
44
+ assert_equal(0, counts["fail"], "tester should have no assertion failures")
45
+ assert_equal(0, counts["error"], "tester should not have encountered any errors")
46
+ assert(counts["total"] > 0, "tester should have performed at least a single test")
47
+ end
48
+ end
49
+
50
+ assert(contained_summary, "the tester must have produced a complete output stream")
51
+ end
52
+ end
53
+
54
+ context "with Glowworm server and threadless tester" do
55
+ setup_once do
56
+ tester_tapy = results_path('basic_server_nbg_test.yaml')
57
+
58
+ # The tester command outputs results via TAP-Y and diagnostics to STDERR
59
+ tester_cmd = cmd_in_client_ruby_dist(BASIC_SERVER_TESTER_BIN, "--server #{GW_URI} --nbg true | tee #{tester_tapy}")
60
+ @topology = start_tester_topology(tester_cmd)
61
+
62
+ STDERR.puts "tester status #{@topology[:gw_tester].status}"
63
+
64
+ @tester_results = Psych.parse_stream(IO.read(tester_tapy)).to_ruby
65
+ end
66
+
67
+ teardown_once do
68
+ @topology.stop_all
69
+
70
+ # TODO(noah): Hideous hack until we have real RVM/Nodule integration
71
+ system("pkill -9 -f glowworm_server.rb")
72
+ end
73
+
74
+ should "test server response" do
75
+ contained_summary = false
76
+ @tester_results.each do |tap_entry|
77
+ if "final" == tap_entry["type"]
78
+ contained_summary = true
79
+ counts = tap_entry["counts"]
80
+
81
+ assert_equal(0, counts["fail"], "tester should have no assertion failures")
82
+ assert_equal(0, counts["error"], "tester should not have encountered any errors")
83
+ assert(counts["total"] > 0, "tester should have performed at least a single test")
84
+ end
85
+ end
86
+
87
+ assert(contained_summary, "the tester must have produced a complete output stream") unless ENV["TEST_GLOWWORM_VERSION"].nil? || ENV["TEST_GLOWWORM_VERSION"] < "0.1.6"
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "ecology"
4
+ require "sequel"
5
+ require "termite"
6
+
7
+ # Can be overridden with ECOLOGY_SPEC
8
+ Ecology.read("glowworm_server.ecology")
9
+
10
+ db_spec = Ecology.property("db") || { :adapter => 'sqlite', :database => 'test_data.sqlite' }
11
+ STDERR.puts "Connecting to #{db_spec.inspect}"
12
+ DB = Sequel.connect db_spec
13
+ MyLogger = Termite::Logger.new
14
+
15
+ # The table creation below duplicates a lot of stuff from
16
+ # server/db_migrations. Alas.
17
+
18
+ DB.create_table!(:account_sets) do
19
+ primary_key :id
20
+ Datetime :created_at, :null => false
21
+ Datetime :updated_at, :null => false
22
+ String :name, :null => false
23
+ String :description
24
+ index :name, :unique => true
25
+ end
26
+
27
+ DB.create_table!(:features) do
28
+ primary_key :id
29
+ Datetime :created_at, :null => false
30
+ Datetime :updated_at, :null => false
31
+ String :name, :null => false
32
+ String :description
33
+ Boolean :fully_active, :default => false
34
+ index :name, :unique => true
35
+ end
36
+
37
+ DB.create_table!(:account_set_accounts) do
38
+ primary_key :id
39
+ Datetime :added_at, :null => false
40
+ Integer :account_set_id, :null => false
41
+ Integer :provider_id, :null => true
42
+ String :account, :limit => 10, :null => false
43
+ index :account_set_id
44
+ index :provider_id
45
+ index :account
46
+ index [:account_set_id, :account], :unique => true
47
+ end
48
+
49
+ DB.create_table!(:account_set_features) do
50
+ primary_key :id
51
+ Datetime :added_at, :null => false
52
+ Integer :account_set_id, :null => false
53
+ Integer :feature_id, :null => false
54
+ Integer :value, :null => false, :default => 1
55
+ index :feature_id
56
+ index [:account_set_id, :feature_id], :unique => true
57
+ end
58
+
59
+ DB.create_table!(:feature_account_overrides) do
60
+ Integer :provider_id, :null => true
61
+ String :account, :limit => 10, :null => false
62
+ Integer :feature_id, :null => false
63
+ Integer :value, :null => false
64
+ index :provider_id
65
+ index :feature_id
66
+ index [:account, :feature_id], :unique => true
67
+ end
68
+
69
+ # Probably gone already, but let's be sure.
70
+ DB[:account_sets].delete
71
+ DB[:features].delete
72
+ DB[:account_set_accounts].delete
73
+ DB[:account_set_features].delete
74
+ DB[:feature_account_overrides].delete
75
+
76
+ def unique_id
77
+ @id_counter ||= 10_000
78
+ @id_counter += 1
79
+ @id_counter
80
+ end
81
+
82
+ # "Hello, world" for features - feature_flag(123, "foo") should return true.
83
+ FOO_FEATURE = 1
84
+ DB[:features].insert(:id => FOO_FEATURE, :name => "foo", :description => "hello world feature",
85
+ :created_at => Time.now, :updated_at => Time.now)
86
+ DB[:feature_account_overrides].insert(:account => "123", :provider_id => 123, :feature_id => FOO_FEATURE,
87
+ :value => 1)
88
+
89
+ # Accounts sets for basic testing
90
+ ONE_HORSE_TOWN = 1
91
+ DB[:account_sets].insert(:id => ONE_HORSE_TOWN, :name => "solo", :description => "one-horse town",
92
+ :created_at => Time.now, :updated_at => Time.now)
93
+ TWO_HORSE_TOWN = 2
94
+ DB[:account_sets].insert(:id => TWO_HORSE_TOWN, :name => "duo", :description => "two-horse town",
95
+ :created_at => Time.now, :updated_at => Time.now)
96
+ NO_HORSE_TOWN = 3
97
+ DB[:account_sets].insert(:id => NO_HORSE_TOWN, :name => "nowhere", :description => "no-horse town",
98
+ :created_at => Time.now, :updated_at => Time.now)
99
+
100
+ # Account sets for get_value testing
101
+ LOW_ID_SET = 4
102
+ DB[:account_sets].insert(:id => LOW_ID_SET, :name => "low-id", :description => "acct set, low id #",
103
+ :created_at => Time.now, :updated_at => Time.now)
104
+ BIG_VALUE_SET = 5
105
+ DB[:account_sets].insert(:id => BIG_VALUE_SET, :name => "big-val",
106
+ :description => "acct set, big value",
107
+ :created_at => Time.now, :updated_at => Time.now)
108
+ LITTLE_VALUE_SET = 6
109
+ DB[:account_sets].insert(:id => LITTLE_VALUE_SET, :name => "little-val",
110
+ :description => "acct set, little value",
111
+ :created_at => Time.now, :updated_at => Time.now)
112
+
113
+ VALUE_FOR_ACCT_SET = {
114
+ ONE_HORSE_TOWN => 1,
115
+ TWO_HORSE_TOWN => 2,
116
+ BIG_VALUE_SET => 100,
117
+ LITTLE_VALUE_SET => 7,
118
+ LOW_ID_SET => 21,
119
+ }
120
+
121
+ # Features in 0, 1 and 2 account sets, with no override, true override and false override
122
+ [false, true].each do |fully_active|
123
+ [nil, true, false].each do |override|
124
+ [[],
125
+ [ONE_HORSE_TOWN],
126
+ [ONE_HORSE_TOWN, TWO_HORSE_TOWN],
127
+ ].each do |acct_sets|
128
+ offset = override.nil? ? 0 : (override == true ? 1 : 2)
129
+ offset += 3 if fully_active
130
+ num_accts = acct_sets.size
131
+ id = 1000 + offset * 100 + num_accts
132
+ desc = "#{fully_active ? "Fully Active " : ""}Feature w/ #{num_accts} acct sets and override #{override.inspect}"
133
+ account = "account_for_acct_sets_#{num_accts}_#{override.inspect}"
134
+ feature = "#{fully_active ? "fully_active_" : ""}feature_for_acct_sets_#{num_accts}_#{override.inspect}"
135
+
136
+ DB[:features].insert(:id => id, :name => feature, :description => desc,
137
+ :created_at => Time.now, :updated_at => Time.now, :fully_active => fully_active)
138
+
139
+ acct_sets.each do |acct_set|
140
+ DB[:account_set_accounts].insert(:id => unique_id, :account_set_id => acct_set, :account => account,
141
+ :added_at => Time.now) unless fully_active # Only need to do this once
142
+ DB[:account_set_features].insert(:id => unique_id, :account_set_id => acct_set, :feature_id => id,
143
+ :value => fully_active ? 0 : 1, :added_at => Time.now) # Opposite of default
144
+ end
145
+
146
+ case override
147
+ when true
148
+ DB[:feature_account_overrides].insert(:account => account, :feature_id => id, :value => 1)
149
+ when false
150
+ DB[:feature_account_overrides].insert(:account => account, :feature_id => id, :value => 0)
151
+ when nil
152
+ # No-op
153
+ else
154
+ raise "Illegal value for override!"
155
+ end
156
+ end
157
+ end
158
+ end
159
+
160
+ [nil, 0, 1, 2, 5].each do |override|
161
+ counter = 0
162
+ [[],
163
+ [LITTLE_VALUE_SET],
164
+ [BIG_VALUE_SET, LITTLE_VALUE_SET],
165
+ [BIG_VALUE_SET, LITTLE_VALUE_SET, LOW_ID_SET]].each do |acct_sets|
166
+ acct_key = acct_sets.map(&:to_s).join("/")
167
+ id = 2000 + (override.nil? ? 0 : override + 1) * 100 + counter
168
+ counter += 1
169
+ desc = "Feature w/ value and w/ acct sets #{acct_key} and override #{override.inspect}"
170
+ account = "account_for_value_acct_sets_#{acct_key}_#{override.inspect}"
171
+ feature = "feature_for_value_acct_sets_#{acct_key}_#{override.inspect}"
172
+
173
+ DB[:features].insert(:id => id, :name => feature, :description => desc,
174
+ :created_at => Time.now, :updated_at => Time.now)
175
+
176
+ acct_sets.each do |acct_set|
177
+ DB[:account_set_accounts].insert(:id => unique_id, :account_set_id => acct_set, :account => account,
178
+ :added_at => Time.now)
179
+ DB[:account_set_features].insert(:id => unique_id, :account_set_id => acct_set, :feature_id => id,
180
+ :value => VALUE_FOR_ACCT_SET[acct_set], :added_at => Time.now)
181
+ end
182
+
183
+ if override != nil
184
+ DB[:feature_account_overrides].insert(:account => account, :feature_id => id,
185
+ :value => override)
186
+ end
187
+ end
188
+ end
189
+
190
+ $stderr.puts "Features: #{DB[:features].count}"
191
+ $stderr.puts "Account Sets: #{DB[:account_sets].count}"
192
+ $stderr.puts "Accounts/set joins: #{DB[:account_set_accounts].count}"
193
+ $stderr.puts "Set/feature joins: #{DB[:account_set_features].count}"
194
+ $stderr.puts "Overrides: #{DB[:feature_account_overrides].count}"
195
+
196
+ $stderr.puts "Added test data successfully!"
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.join(File.dirname(__FILE__), "integration_test_helper")
4
+ require "net/http"
5
+ require 'multi_json'
6
+ require "trollop"
7
+ require "psych"
8
+
9
+ EM_SERVER_TESTER_BIN = "#{GLOWWORM_ROOT}/bin/em_server_tester"
10
+ EM_SERVER_CONFIG = "#{GLOWWORM_ROOT}/bin/em_server/config.ru"
11
+ EM_PORT = Nodule::Util.random_tcp_port
12
+ EM_HOST = "http://localhost:#{EM_PORT}"
13
+
14
+ class EMServerTest < Scope::TestCase
15
+ setup_once do
16
+ system("ECOLOGY_SPEC=\"#{GLOWWORM_ECOLOGY_FILE}\" #{GLOWWORM_ROOT}/test/integration/create_sqlite_data.rb")
17
+ fail "Couldn't create SQLite data successfully!" unless $?.success?
18
+ end
19
+
20
+ context "with Glowworm server and tester" do
21
+ setup_once do
22
+ tester_tapy = results_path('em_server_test.yaml')
23
+
24
+ # The tester command outputs results via TAP-Y and diagnostics to STDERR
25
+ tester_cmd = cmd_in_client_ruby_dist(EM_SERVER_TESTER_BIN, "--server #{EM_HOST} | tee #{tester_tapy}")
26
+ # The tester may be run under ruby 1.8. Fake like 1.9 by using RUBYOPT to include rubygems
27
+ # support
28
+ em_server = {
29
+ :em_server => Nodule::Process.new({
30
+ "ECOLOGY_SPEC" => GLOWWORM_ECOLOGY_FILE,
31
+ "GW_URI" => GW_URI,
32
+ "GLOWWORM_ROOT" => GLOWWORM_ROOT },
33
+ "bash", "-l", "-c", "cd #{GLOWWORM_ROOT} && bundle exec thin -p #{EM_PORT} -R #{EM_SERVER_CONFIG} start",
34
+ :stdout => :capture, :stderr => :capture, :verbose => :cyanio
35
+ )
36
+ }
37
+ @topology = start_tester_topology(tester_cmd, 10, em_server)
38
+
39
+ STDERR.puts "tester status #{@topology[:gw_tester].status}"
40
+
41
+ @tester_results = Psych.parse_stream(IO.read(tester_tapy)).to_ruby
42
+ end
43
+
44
+ teardown_once do
45
+ @topology.stop_all
46
+
47
+ # TODO(noah): Hideous hack until we have real RVM/Nodule integration
48
+ system("pkill -9 -f glowworm_server.rb")
49
+ system("pkill -9 -f thin")
50
+ end
51
+
52
+ should "test server response" do
53
+ contained_summary = false
54
+ @tester_results.each do |tap_entry|
55
+ if "final" == tap_entry["type"]
56
+ contained_summary = true
57
+ counts = tap_entry["counts"]
58
+
59
+ assert_equal(0, counts["fail"], "tester should have no assertion failures")
60
+ assert_equal(0, counts["error"], "tester should not have encountered any errors")
61
+ assert(counts["total"] > 0, "tester should have performed at least a single test")
62
+ end
63
+ end
64
+
65
+ assert(contained_summary, "the tester must have produced a complete output stream")
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,17 @@
1
+ source "http://rubygems.org"
2
+ source "http://gems.sv2" # Ooyala-specific!
3
+ source "http://gems.us-east-1.ooyala.com:8080"
4
+
5
+ fail "requires TEST_GLOWWORM_VERSION in environment" unless ENV['TEST_GLOWWORM_VERSION']
6
+
7
+ gem "glowworm", ENV['TEST_GLOWWORM_VERSION']
8
+
9
+ # plus development dependencies
10
+ gem "ansi"
11
+ gem "minitap", ">=0.3.5"
12
+ gem "minitest"
13
+ gem "mocha"
14
+ gem "rake"
15
+ gem "scope"
16
+ gem "tapout"
17
+ gem "httparty"