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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8b9983c59a3ba2c697b2d11c20cdb4d8a8a236f8
4
+ data.tar.gz: 979315d46ed457760f89ff5692b475b69e94cfc5
5
+ SHA512:
6
+ metadata.gz: 5019047e53c66b01505a3a77c037f79fb395b342b5e3962dd33edb10e97803a20f0da5bc75211e65b64f9ac29211db6317af3a386ebc665f18a079e7f964375d
7
+ data.tar.gz: 89107f6c920895540a00093cde7eb03df1e1a3f160935ebf75e1afb8d6a4eb4cb156427bd2cb78802ad3d5c74755e39413d76f3e5ef6ec6446ad9c0b7886c09c
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
@@ -0,0 +1,2 @@
1
+ MethodLength:
2
+ Max: 43
@@ -0,0 +1,10 @@
1
+ before_install:
2
+ - bundle install --path .bundle
3
+ rvm:
4
+ - 1.9.3
5
+ - 2.1.0
6
+ - 2.1.2
7
+ branches:
8
+ only:
9
+ - master
10
+ script: "bundle exec rake spec rspec"
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in blender.gemspec
4
+ gemspec
5
+
6
+ gem 'ruby-prof'
@@ -0,0 +1,14 @@
1
+ Copyright:: Copyright (c) 2014 PagerDuty, Inc.
2
+ License:: Apache License, Version 2.0
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
@@ -0,0 +1,342 @@
1
+ [![Built on Travis](https://secure.travis-ci.org/PagerDuty/blender.png?branch=master)](http://travis-ci.org/PagerDuty/blender)
2
+ # Blender
3
+
4
+ Blender is a modular remote command execution framework. Blender provides few basic
5
+ primitives to automate cross server workflows. Workflows can be expressed in plain
6
+ ruby DSL and executed using the CLI.
7
+
8
+ Following is an example of a simple blender script that will update the package
9
+ index of three ubuntu servers.
10
+
11
+ ```ruby
12
+ # example.rb
13
+ ssh_task 'update' do
14
+ execute 'sudo apt-get update -y'
15
+ members ['ubuntu01', 'ubuntu02', 'ubuntu03']
16
+ end
17
+ ```
18
+
19
+ Which can execute it as:
20
+ ```sh
21
+ blend -f example.rb
22
+ ```
23
+ Output:
24
+ ```
25
+ Run[example.rb] started
26
+ 3 job(s) computed using 'Default' strategy
27
+ Job 1 [update on ubuntu01] finished
28
+ Job 2 [update on ubuntu02] finished
29
+ Job 3 [update on ubuntu03] finished
30
+ Run finished (42.228923876 s)
31
+ ```
32
+ An workflow can have multiple tasks, individual tasks can have different members
33
+ which can be run in parallel.
34
+
35
+ ```ruby
36
+ # example.rb
37
+ ssh_task 'update' do
38
+ execute 'sudo apt-get update -y'
39
+ members ['ubuntu01', 'ubuntu02', 'ubuntu03']
40
+ end
41
+
42
+ ssh_task 'install' do
43
+ execute 'sudo apt-get install screen -y'
44
+ members ['ubuntu01', 'ubuntu03']
45
+ end
46
+
47
+ concurrency 2
48
+ ```
49
+ Output:
50
+ ```sh
51
+ Run[blends/example.rb] started
52
+ 5 job(s) computed using 'Default' strategy
53
+ Job 1 [update on ubuntu01] finished
54
+ Job 2 [update on ubuntu02] finished
55
+ Job 4 [install on ubuntu01] finished
56
+ Job 3 [update on ubuntu03] finished
57
+ Job 5 [install on ubuntu03] finished
58
+ Run finished (4.462043017 s)
59
+ ```
60
+
61
+ Blender provides various types of task execution (like arbitrary ruby code,
62
+ commands over ssh, serf handlers etc) which can ease automating large cluster
63
+ maintenance, multi stage provisioning, establishing cross server feedback
64
+ loops etc.
65
+
66
+ ## Concepts
67
+
68
+ Blender is composed of two components:
69
+
70
+ * **Tasks and drivers** - Tasks encapsulate commands (or equivalent abstraction). A blender
71
+ script can have multiple tasks. Tasks are executed using drivers. Tasks can declare their
72
+ target hosts.
73
+
74
+ * **Scheduling stratgy** - Determines the order of task execution across the hosts.
75
+ Every blender scripts has one and only one scheduling strategy. Scheduling strategies
76
+ uses the task list as input and produces a list of jobs, to be executed using drivers.
77
+
78
+
79
+ ### Tasks
80
+
81
+ Tasks and drivers compliment each other. Tasks act as front end, where we declare
82
+ what needs to be done, while drivers are used to interpret how those tasks can be done.
83
+ For example `ssh_task` can be used to declare tasks, while `ssh` and `ssh_multi` driver
84
+ can execute `ssh_task`s. Blender core ships with following tasks and drivers:
85
+
86
+ - **shell_task**: execute commands on current host. shell tasks can only have 'localhost'
87
+ as its members. presence of any other hosts in members list will raise exception. shell_tasks
88
+ are executed using shell_out driver.
89
+ Example:
90
+ ```ruby
91
+ shell_task 'foo' do
92
+ execute 'sudo apt-get update -y'
93
+ end
94
+ ```
95
+
96
+ - **ruby_task**: execute ruby blocks against current host. host names from members list is passed
97
+ to the block. ruby_tasks are executed using `Blender::Ruby` driver.
98
+ Example:
99
+ ```ruby
100
+ ruby_task 'baz' do
101
+ execute do |host|
102
+ puts "Host name is: #{host}"
103
+ end
104
+ end
105
+ ```
106
+
107
+ - **ssh_task**: execute commands against remote hosts using ssh. Blender ships with two ssh drivers,
108
+ one based on a vanilla Ruby `net-ssh` binding, another based on `net-ssh-multi` (which supports parallel
109
+ execution)
110
+ Example:
111
+ ```ruby
112
+ ssh_task 'bar' do
113
+ execute 'sudo apt-get update -y'
114
+ members ['host1', 'host2']
115
+ end
116
+ ```
117
+
118
+ As mentioned earlier tasks are executed using drivers. Tasks can declare their preferred driver or
119
+ Blender will assign a driver to them automatically. Blender will reuse the global driver if its
120
+ compatible, else it will create one. By default the ```global_driver``` is a ```shell_out``` driver.
121
+ Drivers can expose host concurrency, stdout/stderr streaming and various other customizations,
122
+ specific to their own implementations.
123
+
124
+ ### Scheduling strategies
125
+
126
+ Scheduling strategies are the most crucial part of a blender script. They decide the
127
+ order of command execution across distributed nodes in blender. Each blender script is
128
+ invoked using one strategy. Consider them as a transformation, where the input is tasks and ouput is
129
+ jobs. Tasks and job are pretty similar in their structures (both holds command and hosts),
130
+ except a jobs can hold multiple tasks within them. We'll come to this later, but first, lets
131
+ see how the default strategy work.
132
+
133
+ - **default strategy**: the default strategy takes the list of declared tasks (and associated members
134
+ in each tasks) breaks them up into per node jobs.
135
+ For example:
136
+
137
+ ```ruby
138
+ members ['host1', 'host2', 'host3']
139
+
140
+ ruby_task 'test' do
141
+ execute do |host|
142
+ Blender::Log.info(host)
143
+ end
144
+ end
145
+ ```
146
+
147
+ will result in 3 jobs. each with `ruby_task[test]` on host1, `ruby_task[test]` on host2 and
148
+ `ruby_task[test]` on host3. And then these three tasks will be executed serially.
149
+ Following will create 6 jobs.
150
+
151
+ ```ruby
152
+ members ['host1', 'host2', 'host3']
153
+
154
+ ruby_task 'test 1' do
155
+ execute do |host|
156
+ Blender::Log.info("test 1 on #{host}")
157
+ end
158
+ end
159
+
160
+ ruby_task 'test 2' do
161
+ execute do |host|
162
+ Blender::Log.info("test 2 on #{host}")
163
+ end
164
+ end
165
+ ```
166
+
167
+ While the next one will create 4 jobs (second task will give only one job).
168
+
169
+ ```ruby
170
+ members ['host1', 'host2', 'host3']
171
+
172
+ ruby_task 'test 1' do
173
+ execute do |host|
174
+ Blender::Log.info("test 1 on #{host}")
175
+ end
176
+ end
177
+
178
+ ruby_task 'test 2' do
179
+ execute do |host|
180
+ Blender::Log.info("test 2 on #{host}")
181
+ end
182
+ members ['host3']
183
+ end
184
+ ```
185
+ The default strategy is conservative, and allows drivers that work against a single remote
186
+ host to be integrated with blender. Also this allows the highest level of fine grain job control.
187
+
188
+ Apart from the default strategy, Blender ships with two more strategy, they are:
189
+
190
+ - **per task strategy**: this creates one job per task. Following example will
191
+ create 2 jobs, each with three hosts and one of the `ruby_task` in them.
192
+
193
+ ```ruby
194
+ members ['host1', 'host2', 'host3']
195
+
196
+ strategy :per_task
197
+
198
+ ruby_task 'test 1' do
199
+ execute do |host|
200
+ Blender::Log.info("test 1 on #{host}")
201
+ end
202
+ end
203
+
204
+ ruby_task 'test 2' do
205
+ execute do |host|
206
+ Blender::Log.info("test 2 on #{host}")
207
+ end
208
+ end
209
+ ```
210
+
211
+ per task strategy allows drivers to optimize individual command execution accross multiple hosts. For
212
+ example `ssh_multi` driver allows parallel command execution across many hosts. And can be used
213
+ as:
214
+ ```ruby
215
+ strategy :per_task
216
+ global_driver(:ssh_multi, concurrency: 50)
217
+ ssh_task 'run chef' do
218
+ execute 'sudo chef-client --no-fork'
219
+ end
220
+ ```
221
+ Note: if we use the default strategy, ssh_multi driver wont be able to leverage its
222
+ concurrency features, as the resultant jobs (the driver will receive) will have only one host.
223
+
224
+ - **per host strategy**: it creates one job per host. Following example will create
225
+ 3 jobs. each with one host and 2 ruby tasks. Thus two tasks will be executed in one
226
+ host, then on the next one.. follow on. Think of deployments with rolling restart like
227
+ scenarios. This also allows drivers to optimize multiple tasks/commandsi execution
228
+ against individual hosts (session reuse etc).
229
+
230
+ ```ruby
231
+ strategy :per_host
232
+ members ['host1', 'host2', 'host3']
233
+
234
+ ruby_task 'test 1' do
235
+ execute do |host|
236
+ Blender::Log.info("test 1 on #{host}")
237
+ end
238
+ end
239
+ ruby_task 'test 2' do
240
+ execute do |host|
241
+ Blender::Log.info("test 2 on #{host}")
242
+ end
243
+ end
244
+ ```
245
+ Note: this strategy does not work if you have different hosts per tasks.
246
+
247
+ Its fairly easy to write custom scheduling strategies and they can be used to rewrite or
248
+ rearrange hosts/tasks as you wish. For example, null strategy that return 0 jobs irrespective
249
+ of what tasks or members you pass, or a custome strategy that takes the hosts lists of every
250
+ tasks and considers only one of them dynamically based on some metrics for jobs, etc.
251
+
252
+ ### Host discovery
253
+
254
+ For workflows that depends on dynamic infrastructure, where host names are changing,
255
+ Blender provides abstractions that facilitate discovering them.
256
+ [blender-chef](https://github.com/PagerDuty/blender-chef) and
257
+ [blender-serf](https://github.com/PagerDuty/blender-serf) uses this and allows remote job orchestration
258
+ for chef or serf managed infrastructure.
259
+
260
+ Following are some examples:
261
+
262
+ - **serf**: discover hosts using serf membership
263
+
264
+ ```ruby
265
+ require 'blender/serf'
266
+
267
+ ruby_task 'print host name' do
268
+ execute do |host|
269
+ Blender::Log.info("Host: #{host}")
270
+ end
271
+ members search(:serf, name: '^lt-.*$')
272
+ end
273
+ ```
274
+
275
+ - **chef**: discover hosts using Chef search
276
+
277
+ ```ruby
278
+ require 'blender/dscoveries/chef'
279
+
280
+ ruby_task 'print host name' do
281
+ execute do |host|
282
+ Blender::Log.info("Host: #{host}")
283
+ end
284
+ members search(:chef, 'roles:web')
285
+ end
286
+ ```
287
+
288
+ ## Invoking blender periodially with Rufus schedler
289
+
290
+ Blender is designed to be used as a standalone script that can be invoked on-demand or
291
+ consumed as a library, i.e. workflows are written in plain Ruby objects and invoked
292
+ from other tools or application. Apart from these, Blender can be use for periodic
293
+ job execution also. Underneath it uses `Rufus::Scheduler` to trigger Blender run, after
294
+ a fixed interval (can be expressed via cron syntax as well, thanks to Rufus).
295
+
296
+ Following will run `example.rb` blender script after every 4 hours.
297
+ ```ruby
298
+ schedule '/path/to/example.rb' do
299
+ cron '* */4 * * *'
300
+ end
301
+ ```
302
+
303
+ ## Ignore failure
304
+
305
+ Blender will fail the execution immediately if any of the job fails. `ignore_failure`
306
+ attribute can be used to proceed execution even after failure. This can be declared
307
+ both per task level as well as globally.
308
+
309
+ ```ruby
310
+ shell_task 'fail' do
311
+ command 'ls /does/not/exists'
312
+ ignore_failure true
313
+ end
314
+ shell_task 'will be executed' do
315
+ command 'echo "Thrust is what we need"'
316
+ end
317
+ ```
318
+
319
+
320
+ ## Event handlers
321
+
322
+ Blender provides an event dispatchment facility (inspired from Chef), where arbitrary logic can
323
+ be hooked into the event system (e.g. HipChat notification handlers, statsd handlers, etc) and blender will automatically invoke them during key events. As of now, events are available before and after run and per job execution. Event dispatch system is likely to get more elaborate and blender might have few common event handlers (metric, notifications etc) in near future.
324
+
325
+ ## Supported ruby versions
326
+
327
+ Blender currently support the following Ruby implementations:
328
+
329
+ * *Ruby 1.9.3*
330
+ * *Ruby 2.1.0*
331
+ * *Ruby 2.1.2*
332
+
333
+ ## License
334
+ [Apache 2](http://www.apache.org/licenses/LICENSE-2.0)
335
+
336
+ ## Contributing
337
+
338
+ 1. Fork it ( https://github.com/PagerDuty/blender/fork )
339
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
340
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
341
+ 4. Push to the branch (`git push origin my-new-feature`)
342
+ 5. Create a new Pull Request
@@ -0,0 +1,21 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rubocop/rake_task'
3
+ require 'rspec/core/rake_task'
4
+ require 'yard'
5
+
6
+ YARD::Rake::YardocTask.new do |t|
7
+ t.files = ['lib/**/*.rb']
8
+ end
9
+
10
+ RSpec::Core::RakeTask.new(:spec) do |t|
11
+ t.pattern = %w{spec/**/*_spec.rb}
12
+ end
13
+
14
+ RSpec::Core::RakeTask.new(:rspec) do |t|
15
+ t.pattern = %w{spec/**/*_rspec.rb}
16
+ end
17
+
18
+ RuboCop::RakeTask.new(:rubocop) do |t|
19
+ t.patterns = %w{Rakefile Gemfile lib/**/*.rb}
20
+ t.fail_on_error = true
21
+ end
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Author:: Ranjib Dey (<ranjib@pagerduty.com>)
4
+ # Copyright:: Copyright (c) 2014 PagerDuty, Inc.
5
+ # License:: Apache License, Version 2.0
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+ require 'blender/cli'
20
+ Blender::CLI.start(ARGV.dup)