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