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.
- checksums.yaml +7 -0
- data/README.md +69 -0
- data/chef_ab.gemspec +18 -0
- data/lib/chef-ab.rb +4 -0
- data/lib/chef-ab/base_upgrader.rb +26 -0
- data/lib/chef-ab/ip_metric.rb +13 -0
- data/lib/chef-ab/time_ipbased_upgrader.rb +36 -0
- data/lib/chef-ab/time_linear_upgrader.rb +33 -0
- data/spec/base_upgrader_spec.rb +13 -0
- data/spec/ip_distance.rb +23 -0
- data/spec/time_ipbased_spec.rb +66 -0
- data/spec/time_linear_spec.rb +62 -0
- metadata +72 -0
checksums.yaml
ADDED
@@ -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
|
data/README.md
ADDED
@@ -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).
|
data/chef_ab.gemspec
ADDED
@@ -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
|
data/lib/chef-ab.rb
ADDED
@@ -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,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
|
data/spec/ip_distance.rb
ADDED
@@ -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
|