chef-ab 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 77e4fcce2dd285b43e963bd9263688cf959bdd3d
4
+ data.tar.gz: c2f1e74f7cce4626153a1d720d23782b387a64e5
5
+ SHA512:
6
+ metadata.gz: 8d344eb8da2aead1961311b03c11246c3a965863421b3be4f742fb710a79ab4818241fbb1932451e06de533a1cc46043ec238e19869ac1e575a6f3fce51ddb6c
7
+ data.tar.gz: 4d4dfd04542bac1e1623946a864badb28d2a8d4893b50bb04009a6aa906e40145f4b0bd4e26c3400370306819dd885b75b9955f34a00bdf7cc969f6282b86c0c
@@ -0,0 +1,69 @@
1
+ Chef AB
2
+ =======
3
+
4
+ chef-ab is a small library to activate code in cookbooks progressively in a cluster.
5
+
6
+ It works like an AB test with increasing population.
7
+
8
+
9
+ Usage
10
+ ----------
11
+
12
+ Very simple example in attribute file of :
13
+
14
+ ```ruby
15
+ # value before upgrade
16
+ default[:a_cookbook][:activate_experimental_feature] = false
17
+
18
+ # plumbing
19
+ one_week_in_secs = 7 * 86400
20
+ start_time = Time.new(2014, 02, 11, 8, 30, 00, "+01:00")
21
+ end_time = start_time + one_week_in_secs
22
+
23
+ upgrade = ChefAB::TimeLinearUpgrader.new(
24
+ node['fqdn'], # we use fqdn as id for the migration
25
+ start_time,
26
+ end_time
27
+ )
28
+
29
+ upgrade.execute do
30
+ # value after upgrade
31
+ default[:a_cookbook][:activate_experimental_feature] = true
32
+ end
33
+
34
+ # sugar for easy status collect
35
+ default[:chef_ab][:experimental_feature_activation] = Time.at(upgrade.expected_activation)
36
+ default[:chef_ab][:experimental_feature_activated] = node[:a_cookbook][:activate_experimental_feature]
37
+ ```
38
+
39
+ Another example, upgrading nodes exponentially depending on distance to a given ip address:
40
+
41
+ ```ruby
42
+ # value before upgrade
43
+ default[:a_cookbook][:activate_experimental_feature] = false
44
+
45
+ # plumbing
46
+ start_time = Time.new(2014, 02, 11, 8, 30, 00, "+01:00")
47
+ period = 3600 # going larger every hour
48
+ first_node = "10.11.12.13"
49
+
50
+ upgrade = ChefAB::TimeIPBasedUpgrader.new node['ipaddress'],
51
+ start_time,
52
+ period,
53
+ first_node
54
+
55
+ upgrade.execute do
56
+ # value after upgrade
57
+ default[:a_cookbook][:activate_experimental_feature] = true
58
+ end
59
+ ```
60
+
61
+
62
+ Warning
63
+ ----------
64
+
65
+
66
+
67
+ - This lib is **not** a substitute to release management, it will solve only the issue of progressive update.
68
+ - It is meant to replace the ssh loop that many uses to upgrades server farms.
69
+ - This library does not give strong garantees on the number of servers that will activate code at the same time (see chef_throttle for that).
@@ -0,0 +1,18 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "chef-ab"
6
+ s.version = '0.1.0'
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ["Grégoire Seux"]
9
+ s.license = "Apache License v2"
10
+ s.summary = %q{AB test like chef conf deployement}
11
+ s.homepage = "http://github.com/kamaradclimber/chef-ab"
12
+ s.description = %q{}
13
+ s.files = `git ls-files`.split("\n")
14
+ s.test_files = `git ls-files -- spec/*`.split("\n")
15
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
16
+
17
+ s.add_development_dependency 'rspec'
18
+ end
@@ -0,0 +1,4 @@
1
+ require 'chef-ab/base_upgrader'
2
+ require 'chef-ab/time_linear_upgrader'
3
+ require 'chef-ab/ip_metric'
4
+ require 'chef-ab/time_ipbased_upgrader'
@@ -0,0 +1,26 @@
1
+ require 'zlib'
2
+
3
+ module ChefAB
4
+ class BaseUpgrader
5
+ attr_accessor :node_id, :hash
6
+
7
+ def initialize(node_id)
8
+ @node_id = node_id
9
+ if node_id.is_a? Integer
10
+ @hash = node_id
11
+ else
12
+ @hash = Zlib.crc32 (node_id.to_s)
13
+ end
14
+ end
15
+
16
+ def should_execute?(threshold)
17
+ @hash < threshold
18
+ end
19
+
20
+ def execute(&block)
21
+ if block_given? && should_execute?
22
+ block.call
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,13 @@
1
+ require 'ipaddress'
2
+
3
+ module IPmetric
4
+ def ip_metric(ip_ref, ip)
5
+ ip1 = IPAddress.parse(ip_ref)
6
+ ip2 = IPAddress.parse(ip)
7
+ dist = (0..32).to_a.reverse.bsearch do |mask|
8
+ ip1.prefix = mask
9
+ ip1.include?(ip2)
10
+ end
11
+ 32 - dist
12
+ end
13
+ end
@@ -0,0 +1,36 @@
1
+ require 'ipaddress'
2
+
3
+ module ChefAB
4
+ class TimeIPBasedUpgrader < BaseUpgrader
5
+
6
+ include IPmetric
7
+
8
+ attr_accessor :start_time, :period
9
+ attr_accessor :initial_ip
10
+
11
+ def initialize(node_ip, start_time, period, initial_ip)
12
+ super(ip_metric(initial_ip, node_ip))
13
+ @start_time = start_time.to_i
14
+ @period = period
15
+ @end_time = 31 * period + @start_time
16
+ end
17
+
18
+ def should_execute?(time = nil)
19
+ time ||= current_time
20
+ threshold = (time - start_time) / @period + 1
21
+ super(threshold)
22
+ end
23
+
24
+ def expected_activation
25
+ (@start_time..@end_time).to_a.bsearch do |fake_time|
26
+ should_execute?(fake_time)
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def current_time
33
+ Time.now.to_i
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,33 @@
1
+ module ChefAB
2
+ class TimeLinearUpgrader < BaseUpgrader
3
+
4
+ attr_accessor :start_time, :end_time
5
+
6
+ def initialize(node_id, start_time, end_time)
7
+ super(node_id)
8
+ @start_time = start_time.to_i
9
+ @end_time = end_time.to_i
10
+ upgrade_span = end_time - start_time
11
+ @hash = @hash % upgrade_span
12
+ end
13
+
14
+ def should_execute?(time = nil)
15
+ time ||= current_time
16
+ threshold = time - start_time
17
+ super(threshold)
18
+ end
19
+
20
+ def expected_activation
21
+ (@start_time..@end_time).to_a.bsearch do |fake_time|
22
+ should_execute?(fake_time)
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def current_time
29
+ Time.now.to_i
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,13 @@
1
+ require 'rspec'
2
+ require_relative '../lib/chef-ab.rb'
3
+
4
+ describe ChefAB::BaseUpgrader do
5
+ it 'should have an integer hash' do
6
+ up = ChefAB::BaseUpgrader.new 5
7
+ expect(up.hash).to be_a_kind_of(Integer)
8
+ end
9
+ it 'should have an integer hash in any case' do
10
+ up = ChefAB::BaseUpgrader.new "testing node"
11
+ expect(up.hash).to be_a_kind_of(Integer)
12
+ end
13
+ end
@@ -0,0 +1,23 @@
1
+ require 'rspec'
2
+
3
+ require_relative '../lib/chef-ab.rb'
4
+
5
+ describe IPMetric do
6
+ include IPMetric
7
+ it 'should give metric zero between an address and itself' do
8
+ dist = ip_metric("192.168.0.34", "192.168.0.34")
9
+ expect(dist).to eq(0)
10
+ end
11
+ it 'should give metric 1 between an address and neighbour' do
12
+ dist = ip_metric("192.168.0.34", "192.168.0.35")
13
+ expect(dist).to eq(1)
14
+ end
15
+ it 'should give metric 8 between an address and next block' do
16
+ dist = ip_metric("192.168.0.34", "192.168.1.35")
17
+ expect(dist).to eq(9)
18
+ end
19
+ it 'should give metric 32 between lowest and highest' do
20
+ dist = ip_metric("0.0.0.0", "255.255.255.255")
21
+ expect(dist).to eq(32)
22
+ end
23
+ end
@@ -0,0 +1,66 @@
1
+ require 'rspec'
2
+
3
+ require_relative '../lib/chef-ab.rb'
4
+
5
+ module ChefAB
6
+ class TimeIPBasedUpgrader
7
+ attr_accessor :current_timer
8
+ def current_time
9
+ @current_timer || Time.now.to_i
10
+ end
11
+ end
12
+ end
13
+
14
+ describe ChefAB::TimeIPBasedUpgrader do
15
+
16
+ def future_upgrade
17
+ ChefAB::TimeIPBasedUpgrader.new "192.168.17.1",
18
+ (Time.now + 10),
19
+ 3600,
20
+ "192.168.17.1"
21
+ end
22
+ def past_upgrade
23
+ ChefAB::TimeIPBasedUpgrader.new "192.168.17.1",
24
+ (Time.now - 10),
25
+ 3600,
26
+ "192.168.17.1"
27
+ end
28
+
29
+ it 'should not execute before start of upgrade' do
30
+ expect(future_upgrade.should_execute?).to be_false
31
+ end
32
+
33
+ it 'should always execute after end of upgrade' do
34
+ expect(past_upgrade.should_execute?).to be_true
35
+ end
36
+
37
+ it 'should not call block before start of upgrade' do
38
+ expect { |b| future_upgrade.execute(&b) }.not_to yield_control
39
+ end
40
+
41
+ it 'should always call block after end of upgrade' do
42
+ expect { |b| past_upgrade.execute(&b) }.to yield_control
43
+ end
44
+
45
+ it 'should not call block before expected_activation and should always do after' do
46
+ start_time = 42 #any timestamp
47
+ period = 10
48
+ up = ChefAB::TimeIPBasedUpgrader.new "192.168.18.23",
49
+ start_time,
50
+ period,
51
+ "192.168.17.1"
52
+
53
+ end_time = 42 + 32 * period
54
+
55
+ first_execute_time = up.expected_activation
56
+
57
+ (start_time..first_execute_time-1).each do |fake_time|
58
+ up.current_timer = fake_time
59
+ expect { |b| up.execute(&b) }.not_to yield_control
60
+ end
61
+ (first_execute_time..end_time).each do |fake_time|
62
+ up.current_timer = fake_time
63
+ expect { |b| up.execute(&b) }.to yield_control
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,62 @@
1
+ require 'rspec'
2
+
3
+ require_relative '../lib/chef-ab.rb'
4
+
5
+ module ChefAB
6
+ class TimeLinearUpgrader
7
+ attr_accessor :current_timer
8
+ def current_time
9
+ @current_timer || Time.now.to_i
10
+ end
11
+ end
12
+ end
13
+
14
+ describe ChefAB::TimeLinearUpgrader do
15
+
16
+ def future_upgrade
17
+ ChefAB::TimeLinearUpgrader.new "testing node", (Time.now + 10), (Time.now + 3600)
18
+ end
19
+ def past_upgrade
20
+ ChefAB::TimeLinearUpgrader.new "testing node", (Time.now - 10), (Time.now - 5)
21
+ end
22
+
23
+ it 'should not execute before start of upgrade' do
24
+ expect(future_upgrade.should_execute?).to be_false
25
+ end
26
+
27
+ it 'should always execute after end of upgrade' do
28
+ expect(past_upgrade.should_execute?).to be_true
29
+ end
30
+
31
+ it 'should not call block before start of upgrade' do
32
+ expect { |b| future_upgrade.execute(&b) }.not_to yield_control
33
+ end
34
+
35
+ it 'should always call block after end of upgrade' do
36
+ expect { |b| past_upgrade.execute(&b) }.to yield_control
37
+ end
38
+
39
+ it 'should not call block before expected_activation and should always do after' do
40
+ start_time = 42 #any timestamp
41
+ end_time = 60
42
+ up = ChefAB::TimeLinearUpgrader.new "testing node", start_time, end_time
43
+
44
+ first_execute_time = up.expected_activation
45
+
46
+ (start_time..first_execute_time-1).each do |fake_time|
47
+ up.current_timer = fake_time
48
+ expect { |b| up.execute(&b) }.not_to yield_control
49
+ end
50
+ (first_execute_time..end_time).each do |fake_time|
51
+ up.current_timer = fake_time
52
+ expect { |b| up.execute(&b) }.to yield_control
53
+ end
54
+ end
55
+
56
+ it 'should return correct expected_activation' do
57
+ start_time = 42 #any timestamp
58
+ end_time = 50
59
+ up = ChefAB::TimeLinearUpgrader.new 5, start_time, end_time
60
+ expect(up.expected_activation).to eq(42 + 5 +1)
61
+ end
62
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: chef-ab
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Grégoire Seux
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-02-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: ''
28
+ email:
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - README.md
34
+ - chef_ab.gemspec
35
+ - lib/chef-ab.rb
36
+ - lib/chef-ab/base_upgrader.rb
37
+ - lib/chef-ab/ip_metric.rb
38
+ - lib/chef-ab/time_ipbased_upgrader.rb
39
+ - lib/chef-ab/time_linear_upgrader.rb
40
+ - spec/base_upgrader_spec.rb
41
+ - spec/ip_distance.rb
42
+ - spec/time_ipbased_spec.rb
43
+ - spec/time_linear_spec.rb
44
+ homepage: http://github.com/kamaradclimber/chef-ab
45
+ licenses:
46
+ - Apache License v2
47
+ metadata: {}
48
+ post_install_message:
49
+ rdoc_options: []
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - '>='
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirements: []
63
+ rubyforge_project:
64
+ rubygems_version: 2.0.3
65
+ signing_key:
66
+ specification_version: 4
67
+ summary: AB test like chef conf deployement
68
+ test_files:
69
+ - spec/base_upgrader_spec.rb
70
+ - spec/ip_distance.rb
71
+ - spec/time_ipbased_spec.rb
72
+ - spec/time_linear_spec.rb