pd-blender 0.0.1

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