chef-ab 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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