pd-blender 0.0.1

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 (62) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.rubocop.yml +2 -0
  4. data/.travis.yml +10 -0
  5. data/Gemfile +6 -0
  6. data/LICENSE.txt +14 -0
  7. data/README.md +342 -0
  8. data/Rakefile +21 -0
  9. data/bin/blend +20 -0
  10. data/blender.gemspec +36 -0
  11. data/lib/blender.rb +67 -0
  12. data/lib/blender/cli.rb +71 -0
  13. data/lib/blender/configuration.rb +45 -0
  14. data/lib/blender/discovery.rb +41 -0
  15. data/lib/blender/drivers/base.rb +40 -0
  16. data/lib/blender/drivers/compound.rb +29 -0
  17. data/lib/blender/drivers/ruby.rb +55 -0
  18. data/lib/blender/drivers/shellout.rb +63 -0
  19. data/lib/blender/drivers/ssh.rb +93 -0
  20. data/lib/blender/drivers/ssh_multi.rb +102 -0
  21. data/lib/blender/event_dispatcher.rb +45 -0
  22. data/lib/blender/exceptions.rb +26 -0
  23. data/lib/blender/handlers/base.rb +39 -0
  24. data/lib/blender/handlers/doc.rb +73 -0
  25. data/lib/blender/job.rb +73 -0
  26. data/lib/blender/lock/flock.rb +64 -0
  27. data/lib/blender/log.rb +24 -0
  28. data/lib/blender/rspec.rb +68 -0
  29. data/lib/blender/rspec/stub_registry.rb +45 -0
  30. data/lib/blender/scheduled_job.rb +66 -0
  31. data/lib/blender/scheduler.rb +114 -0
  32. data/lib/blender/scheduler/dsl.rb +160 -0
  33. data/lib/blender/scheduling_strategies/base.rb +30 -0
  34. data/lib/blender/scheduling_strategies/default.rb +37 -0
  35. data/lib/blender/scheduling_strategies/per_host.rb +38 -0
  36. data/lib/blender/scheduling_strategies/per_task.rb +37 -0
  37. data/lib/blender/tasks/base.rb +72 -0
  38. data/lib/blender/tasks/ruby.rb +31 -0
  39. data/lib/blender/tasks/shell_out.rb +30 -0
  40. data/lib/blender/tasks/ssh.rb +25 -0
  41. data/lib/blender/timer.rb +54 -0
  42. data/lib/blender/utils/refinements.rb +45 -0
  43. data/lib/blender/utils/thread_pool.rb +54 -0
  44. data/lib/blender/utils/ui.rb +51 -0
  45. data/lib/blender/version.rb +20 -0
  46. data/spec/blender/blender_rspec.rb +31 -0
  47. data/spec/blender/discovery_spec.rb +16 -0
  48. data/spec/blender/drivers/ssh_multi_spec.rb +16 -0
  49. data/spec/blender/drivers/ssh_spec.rb +17 -0
  50. data/spec/blender/dsl_spec.rb +19 -0
  51. data/spec/blender/event_dispatcher_spec.rb +17 -0
  52. data/spec/blender/job_spec.rb +42 -0
  53. data/spec/blender/lock_spec.rb +129 -0
  54. data/spec/blender/scheduled_job_spec.rb +30 -0
  55. data/spec/blender/scheduler_spec.rb +140 -0
  56. data/spec/blender/scheduling_strategies/default_spec.rb +75 -0
  57. data/spec/blender/utils/refinements_spec.rb +16 -0
  58. data/spec/blender/utils/thread_pool_spec.rb +16 -0
  59. data/spec/blender_spec.rb +37 -0
  60. data/spec/data/example.rb +12 -0
  61. data/spec/spec_helper.rb +35 -0
  62. metadata +304 -0
@@ -0,0 +1,51 @@
1
+ #
2
+ # Author:: Ranjib Dey (<ranjib@pagerduty.com>)
3
+ # Copyright:: Copyright (c) 2014 PagerDuty, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ require 'highline'
19
+
20
+ module Blender
21
+ module Utils
22
+ class UI
23
+ def initialize
24
+ @mutex = Mutex.new
25
+ @highline = HighLine.new
26
+ end
27
+ def puts(string)
28
+ @mutex.synchronize do
29
+ $stdout.puts(string)
30
+ end
31
+ end
32
+
33
+ def puts_red(string)
34
+ puts(color(string, :red))
35
+ end
36
+
37
+ def puts_cyan(string)
38
+ puts(color(string, :cyan))
39
+ end
40
+
41
+ def puts_green(string)
42
+ puts(color(string, :green))
43
+ end
44
+
45
+ def color(string, *colors)
46
+ @highline.color(string, *colors)
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,20 @@
1
+ #
2
+ # Author:: Ranjib Dey (<ranjib@pagerduty.com>)
3
+ # Copyright:: Copyright (c) 2014 PagerDuty, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ module Blender
19
+ VERSION = '0.0.1'
20
+ end
@@ -0,0 +1,31 @@
1
+ #
2
+ # Author:: Ranjib Dey (<ranjib@pagerduty.com>)
3
+ # Copyright:: Copyright (c) 2014 PagerDuty, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ require 'blender/rspec'
19
+ require 'rspec'
20
+ require 'rspec/mocks'
21
+ require 'rspec/expectations'
22
+ require 'blender'
23
+
24
+ describe Blender::RSpec do
25
+ let(:scheduler) do
26
+ noop_scheduler_from_file(File.expand_path('../../data/example.rb', __FILE__))
27
+ end
28
+ it 'should have 3 tasks' do
29
+ expect(scheduler.tasks.size).to eq(2)
30
+ end
31
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+ require 'blender/discovery'
3
+ describe Blender::Scheduler do
4
+
5
+ let(:scheduler) do
6
+ Blender::Scheduler.new('test')
7
+ end
8
+
9
+ it 'should return Chef is type :chef is passed' do
10
+ class Blender::Discovery::Foo;end
11
+ disco = double(Blender::Discovery::Foo)
12
+ allow(disco).to receive(:search).with('name:xx').and_return(['a', 'b', 'c'])
13
+ allow(Blender::Discovery::Foo).to receive(:new).and_return(disco)
14
+ expect(scheduler.search(:foo, 'name:xx')).to eq(['a', 'b', 'c'])
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe Blender::Driver::SshMulti do
4
+ let(:driver) {described_class.new(events: Object.new, concurrency: 10)}
5
+ let(:job) do
6
+ Blender::Job.new(
7
+ 101,
8
+ nil,
9
+ %w{h1 h2 h3 h4 h5},
10
+ Array.new(1){|n| create_task("t#{n}")}
11
+ )
12
+ end
13
+ it '#concurrency' do
14
+ expect(driver.concurrency).to eq(10)
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe Blender::Driver::Ssh do
4
+ let(:hosts) {['h1']}
5
+ let(:tasks){ Array.new(3){|n| create_task("t#{n}")}}
6
+ let(:driver) {described_class.new(events: Object.new)}
7
+ it 'should execute commands over net ssh channel' do
8
+ channel = double('channel').as_null_object
9
+ session = double('session', open_channel: channel, loop: true)
10
+ expect(Net::SSH).to receive(:start).with(
11
+ 'h1',
12
+ ENV['USER'],
13
+ {}
14
+ ).and_return(session)
15
+ driver.execute(tasks, hosts)
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe '#dsl' do
4
+ let(:scheduler) do
5
+ sched = Blender::Scheduler.new('test')
6
+ sched.instance_eval do
7
+ ssh_task 'run' do
8
+ execute 'sudo /usr/local/sbin/chef-client-cron'
9
+ members ['a']
10
+ end
11
+ end
12
+ sched
13
+ end
14
+
15
+ it '#check DSL' do
16
+ allow_any_instance_of(Blender::Driver::Ssh).to receive(:execute)
17
+ scheduler.run
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe Blender::EventDispatcher do
4
+ let(:dispatcher) do
5
+ described_class.new
6
+ end
7
+ subject(:handler){ Object.new}
8
+ it '#register' do
9
+ dispatcher.register(handler)
10
+ expect(dispatcher.handlers).to include(handler)
11
+ end
12
+ it 'should forward all methods to the registered handlers' do
13
+ dispatcher.register(handler)
14
+ expect(handler).to receive(:run_started)
15
+ dispatcher.run_started
16
+ end
17
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe Blender::Job do
4
+ let(:driver) do
5
+ Object.new
6
+ end
7
+ it '#should use default driver if no tasks are given' do
8
+ job = Blender::Job.new(1, driver , [] ,[])
9
+ expect(job.driver).to eq(driver)
10
+ end
11
+ describe '#name' do
12
+ it 'with one host, one task' do
13
+ t1 = create_task('t1', driver)
14
+ job = Blender::Job.new(1, Object.new, [t1], ['a'])
15
+ expect(job.name).to eq('t1 on a')
16
+ end
17
+ it 'with one host, many tasks' do
18
+ t1 = create_task('t1', driver)
19
+ t2 = create_task('t2', driver)
20
+ job = Blender::Job.new(1, Object.new,[t1, t2], ['a'])
21
+ expect(job.name).to eq('2 tasks on a')
22
+ end
23
+ it 'with 0 hosts, many tasks' do
24
+ t1 = create_task('t1', driver)
25
+ t2 = create_task('t2', driver)
26
+ t3 = create_task('t3', driver)
27
+ job = Blender::Job.new(1, Object.new, [t1, t2, t3], [])
28
+ expect(job.name).to eq('3 tasks on 0 members')
29
+ end
30
+ it 'with many hosts, many tasks' do
31
+ t1 = create_task('t1', driver)
32
+ t2 = create_task('t2', driver)
33
+ job = Blender::Job.new(1, Object.new, [t1, t2], ['a', 'b', 'c'])
34
+ expect(job.name).to eq('2 tasks on 3 members')
35
+ end
36
+ it 'with many hosts one task' do
37
+ t1 = create_task('t1', driver)
38
+ job = Blender::Job.new(1, Object.new, [t1], ['a', 'b', 'c'])
39
+ expect(job.name).to eq('t1 on 3 members')
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,129 @@
1
+ require 'spec_helper'
2
+
3
+ describe Blender::Lock do
4
+
5
+ it 'should not allow two blender run with same name to run at the same time' do
6
+
7
+ pid1 = fork do
8
+ Blender.blend('test-1') do |sched|
9
+ sched.members(['localhost'])
10
+ sched.ruby_task('date') do
11
+ execute do
12
+ sleep 5
13
+ puts 'This will succeed'
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ pid2 = fork do
20
+ STDERR.reopen(File::NULL)
21
+ Blender.blend('test-1') do |sched|
22
+ sched.members(['localhost'])
23
+ sched.ruby_task('date') do
24
+ execute do
25
+ puts 'This will fail'
26
+ end
27
+ end
28
+ end
29
+ end
30
+ status1 = Process.wait2 pid1
31
+ status2 = Process.wait2 pid2
32
+ expect(status1.last.exitstatus).to eq(0)
33
+ expect(status2.last.exitstatus).to_not eq(0)
34
+ end
35
+
36
+ it 'should allow two blender run with different name to run at the same time' do
37
+ pid1 = fork do
38
+ Blender.blend('test-1') do |sched|
39
+ sched.members(['localhost'])
40
+ sched.ruby_task('date') do
41
+ execute do
42
+ sleep 5
43
+ puts 'This will succeed'
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ pid2 = fork do
50
+ Blender.blend('test-2') do |sched|
51
+ sched.members(['localhost'])
52
+ sched.ruby_task('date') do
53
+ execute do
54
+ puts 'This will succeed'
55
+ end
56
+ end
57
+ end
58
+ end
59
+ status1 = Process.wait2 pid1
60
+ status2 = Process.wait2 pid2
61
+ expect(status1.last.exitstatus).to eq(0)
62
+ expect(status2.last.exitstatus).to eq(0)
63
+ end
64
+
65
+ context 'File based locking with timeout' do
66
+
67
+ it 'should raise lock acquisition error when times out' do
68
+ pid1 = fork do
69
+ Blender.blend('test-1') do |sched|
70
+ sched.members(['localhost'])
71
+ sched.ruby_task('date') do
72
+ execute do
73
+ sleep 5
74
+ puts 'This will succeed'
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ pid2 = fork do
81
+ STDERR.reopen(File::NULL)
82
+ Blender.blend('test-1') do |sched|
83
+ sched.members(['localhost'])
84
+ sched.lock_options('flock', timeout: 3)
85
+ sched.ruby_task('date') do
86
+ execute do
87
+ puts 'This will fail'
88
+ end
89
+ end
90
+ end
91
+ end
92
+ status1 = Process.wait2 pid1
93
+ status2 = Process.wait2 pid2
94
+ expect(status1.last.exitstatus).to eq(0)
95
+ expect(status2.last.exitstatus).to_not eq(0)
96
+ end
97
+
98
+ it 'should not raise lock acquisition error when able to acquire lock within timeout period' do
99
+ pid1 = fork do
100
+ Blender.blend('test-1') do |sched|
101
+ sched.members(['localhost'])
102
+ sched.ruby_task('date') do
103
+ execute do
104
+ sleep 5
105
+ puts 'This will succeed'
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ pid2 = fork do
112
+ STDERR.reopen(File::NULL)
113
+ Blender.blend('test-1') do |sched|
114
+ sched.members(['localhost'])
115
+ sched.lock_options('flock', timeout: 3)
116
+ sched.ruby_task('date') do
117
+ execute do
118
+ puts 'This will fail'
119
+ end
120
+ end
121
+ end
122
+ end
123
+ status1 = Process.wait2 pid1
124
+ status2 = Process.wait2 pid2
125
+ expect(status1.last.exitstatus).to eq(0)
126
+ expect(status2.last.exitstatus).to_not eq(0)
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+ require 'blender/scheduled_job'
3
+
4
+ describe Blender::ScheduledJob do
5
+ let(:scheduled_job) do
6
+ Blender::ScheduledJob.new('test job')
7
+ end
8
+ it '#blender_file' do
9
+ scheduled_job.blender_file('test.rb')
10
+ expect(scheduled_job.file).to eq('test.rb')
11
+ end
12
+ it '#cron' do
13
+ time = '*/4 * * * *'
14
+ scheduled_job.cron(time)
15
+ expect(scheduled_job.schedule).to eq([:cron, time])
16
+ end
17
+ it '#every' do
18
+ scheduled_job.every(15)
19
+ expect(scheduled_job.schedule).to eq([:every, 15])
20
+ end
21
+ it '#run' do
22
+ scheduled_job.blender_file('test.rb')
23
+ scheduled_job.every(15)
24
+ sched = double(Blender::Scheduler)
25
+ expect(sched).to receive(:task).with('x')
26
+ expect(File).to receive(:read).with('test.rb').and_return('task "x"')
27
+ expect(Blender).to receive(:blend).with('test.rb').and_yield(sched)
28
+ scheduled_job.run
29
+ end
30
+ end
@@ -0,0 +1,140 @@
1
+ require 'spec_helper'
2
+
3
+ describe Blender::Scheduler do
4
+ let(:scheduler) do
5
+ described_class.new('test')
6
+ end
7
+ describe '#DSL' do
8
+ subject(:task){scheduler.tasks.first}
9
+ it '#ask' do
10
+ tui = double(HighLine)
11
+ allow(HighLine).to receive(:new).and_return(tui)
12
+ expect(tui).to receive(:ask).with('foo')
13
+ scheduler.ask('foo')
14
+ end
15
+ it '#register_handler' do
16
+ handler = Object.new
17
+ scheduler.register_handler(handler)
18
+ expect(scheduler.events.handlers).to include(handler)
19
+ end
20
+ describe '#task' do
21
+ before do
22
+ scheduler.task('whoa')
23
+ end
24
+ it 'should belong to base class' do
25
+ expect(task).to be_kind_of(Blender::Task::Base)
26
+ end
27
+ it 'should use shellout driver' do
28
+ expect(task.driver).to be_kind_of(Blender::Driver::ShellOut)
29
+ end
30
+ it 'should contain only one task' do
31
+ expect(scheduler.tasks.size).to eq(1)
32
+ end
33
+ end
34
+ describe '#ssh_task' do
35
+ before do
36
+ scheduler.ssh_task('test') do
37
+ members ['a']
38
+ execute('ls -l')
39
+ end
40
+ end
41
+ it 'should have correct hosts list' do
42
+ expect(task.hosts).to eq(['a'])
43
+ end
44
+ it 'should have correct command' do
45
+ expect(task.command).to eq('ls -l')
46
+ end
47
+ it 'should use ssh task subclass' do
48
+ expect(task).to be_kind_of(Blender::Task::Ssh)
49
+ end
50
+ it 'should use the ssh driver' do
51
+ expect(task.driver).to be_kind_of(Blender::Driver::Ssh)
52
+ end
53
+ end
54
+ describe '#on' do
55
+ it 'should invoke custom block on specific events' do
56
+ test = 1
57
+ Blender.blend('do it') do |sched|
58
+ sched.on :run_finished do |x|
59
+ test = 2
60
+ end
61
+ sched.task 'ls -alh'
62
+ end
63
+ expect(test).to eq(2)
64
+ end
65
+ end
66
+ describe '#ruby_task' do
67
+ before do
68
+ scheduler.ruby_task('test') do |t|
69
+ t.members ['c']
70
+ t.execute do
71
+ raise 'Fail'
72
+ end
73
+ end
74
+ end
75
+ it 'should setup correct hosts' do
76
+ expect(task.hosts).to eq(['c'])
77
+ end
78
+ it 'should use the ruby task subclass' do
79
+ expect(task).to be_kind_of(Blender::Task::Base)
80
+ end
81
+ it 'should assign the proc as command' do
82
+ expect(task.command).to be_kind_of(Proc)
83
+ end
84
+ it 'should the ruby driver subclass' do
85
+ expect(task.driver).to be_kind_of(Blender::Driver::Ruby)
86
+ end
87
+ end
88
+ describe '#strategy' do
89
+ it '#should raise error for non-existent strategy' do
90
+ expect do
91
+ scheduler.strategy(:foo)
92
+ end.to raise_error(Blender::UnknownSchedulingStrategy)
93
+ end
94
+ it '#get default' do
95
+ expect(scheduler.strategy(:default)).to be_kind_of(Blender::SchedulingStrategy::Default)
96
+ end
97
+ it '#get per_host' do
98
+ expect(scheduler.strategy(:per_host)).to be_kind_of(Blender::SchedulingStrategy::PerHost)
99
+ end
100
+ end
101
+ it '#concurrency' do
102
+ scheduler.concurrency(112)
103
+ expect(scheduler.metadata[:concurrency]).to be(112)
104
+ end
105
+ it '#ignore_failure' do
106
+ scheduler.ignore_failure true
107
+ expect(scheduler.metadata[:ignore_failure]).to be(true)
108
+ end
109
+ it '#members' do
110
+ scheduler.members(['a', 'b'])
111
+ expect(scheduler.metadata[:members]).to eq(['a', 'b'])
112
+ end
113
+ it '#driver' do
114
+ d = scheduler.driver(:ssh, foo: :bar)
115
+ expect(d).to be_kind_of(Blender::Driver::Ssh)
116
+ expect(d.config[:foo]).to be(:bar)
117
+ end
118
+ it 'should have no tasks' do
119
+ expect(scheduler.tasks).to be_empty
120
+ end
121
+ it 'should have no hosts' do
122
+ expect(scheduler.metadata[:members]).to be_empty
123
+ end
124
+ describe '#run' do
125
+ it 'should use serial_run when concurrency is not set' do
126
+ expect(scheduler).to receive(:serial_run)
127
+ scheduler.task 'echo HelloWorld'
128
+ scheduler.run
129
+ end
130
+ it 'should use concurrent_run when concurrency is used' do
131
+ expect(scheduler).to receive(:concurrent_run)
132
+ scheduler.task 'echo HelloWorld1'
133
+ scheduler.task 'echo HelloWorld2'
134
+ scheduler.task 'echo HelloWorld3'
135
+ scheduler.concurrency(2)
136
+ scheduler.run
137
+ end
138
+ end
139
+ end
140
+ end