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,93 @@
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 'net/ssh'
19
+ require 'blender/exceptions'
20
+ require 'blender/drivers/base'
21
+
22
+ module Blender
23
+ module Driver
24
+ class Ssh < Base
25
+
26
+ attr_reader :user
27
+
28
+ def initialize(config = {})
29
+ cfg = config.dup
30
+ @user = cfg.delete(:user) || ENV['USER']
31
+ super(cfg)
32
+ end
33
+
34
+ def execute(tasks, hosts)
35
+ Log.debug("SSH execution tasks [#{Array(tasks).size}]")
36
+ Log.debug("SSH on hosts [#{hosts.join(",")}]")
37
+ Array(hosts).each do |host|
38
+ session = ssh_session(host)
39
+ Array(tasks).each do |task|
40
+ cmd = run_command(task.command, session)
41
+ if cmd.exitstatus != 0 and !task.metadata[:ignore_failure]
42
+ raise ExecutionFailed, cmd.stderr
43
+ end
44
+ end
45
+ session.loop
46
+ end
47
+ end
48
+
49
+ def run_command(command, session)
50
+ password = config[:password]
51
+ command = fixup_sudo(command)
52
+ exit_status = 0
53
+ channel = session.open_channel do |ch|
54
+ ch.request_pty
55
+ ch.exec(command) do |ch, success|
56
+ unless success
57
+ Log.debug("Command not found:#{success.inspect}")
58
+ exit_status = -1
59
+ end
60
+ ch.on_data do |c, data|
61
+ stdout << data
62
+ if data =~ /^blender sudo password: /
63
+ c.send_data("#{password}\n")
64
+ end
65
+ end
66
+ ch.on_extended_data do |c, type, data|
67
+ stderr << data
68
+ end
69
+ ch.on_request "exit-status" do |ichannel, data|
70
+ l = data.read_long
71
+ exit_status = [exit_status, l].max
72
+ Log.debug("exit_status:#{exit_status} , data:#{l}")
73
+ end
74
+ end
75
+ Log.debug("Exit(#{exit_status}) Command: '#{command}'")
76
+ end
77
+ channel.wait
78
+ ExecOutput.new(exit_status, stdout, stderr)
79
+ end
80
+
81
+ private
82
+
83
+ def ssh_session(host)
84
+ Log.debug("Invoking ssh: #{user}@#{host}")
85
+ Net::SSH.start(host, user, config)
86
+ end
87
+
88
+ def fixup_sudo(command)
89
+ command.sub(/^sudo/, 'sudo -p \'blender sudo password: \'')
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,102 @@
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 'net/ssh'
19
+ require 'blender/exceptions'
20
+ require 'blender/drivers/ssh'
21
+
22
+ module Blender
23
+ module Driver
24
+ class SshMulti < Ssh
25
+
26
+ def execute(tasks, hosts)
27
+ Log.debug("SSH execution tasks [#{tasks.size}]")
28
+ Log.debug("SSH on hosts [#{hosts.join("\n")}]")
29
+ session = ssh_multi_session(hosts)
30
+ Array(tasks).each do |task|
31
+ cmd = run_command(task.command, session)
32
+ if cmd.exitstatus != 0 and !task.metadata[:ignore_failure]
33
+ raise ExecutionFailed, cmd.stderr
34
+ end
35
+ end
36
+ session.loop
37
+ end
38
+
39
+ def run_command(command, session)
40
+ password = @config[:password]
41
+ command = fixup_sudo(command)
42
+ exit_status = 0
43
+ channel = session.open_channel do |ch|
44
+ ch.request_pty
45
+ ch.exec(command) do |ch, success|
46
+ unless success
47
+ Log.debug("Command not found:#{success.inspect}")
48
+ exit_status = -1
49
+ end
50
+ ch.on_data do |c, data|
51
+ stdout << data
52
+ if data =~ /^blender sudo password: /
53
+ c.send_data("#{password}\n")
54
+ end
55
+ end
56
+ ch.on_extended_data do |c, type, data|
57
+ stderr << data
58
+ end
59
+ ch.on_request "exit-status" do |ichannel, data|
60
+ l = data.read_long
61
+ exit_status = [exit_status, l].max
62
+ Log.debug("exit_status:#{exit_status} , data:#{l}")
63
+ end
64
+ end
65
+ Log.debug("Exit(#{exit_status}) Command: '#{command}'")
66
+ end
67
+ channel.wait
68
+ ExecOutput.new(exit_status, stdout, stderr)
69
+ end
70
+
71
+ def concurrency
72
+ @config[:concurrency]
73
+ end
74
+
75
+ private
76
+
77
+ def ssh_multi_session(hosts)
78
+ user = @config[:user] || ENV['USER']
79
+ ssh_config = { password: @config[:password]}
80
+ error_handler = lambda do |server|
81
+ if config[:ignore_on_failure]
82
+ $!.backtrace.each { |l| Blender::Log.debug(l) }
83
+ else
84
+ throw :go, :raise
85
+ end
86
+ end
87
+ s = Net::SSH::Multi.start(
88
+ concurrent_connections: concurrency,
89
+ on_error: error_handler
90
+ )
91
+ hosts.each do |h|
92
+ s.use(user + '@' + h)
93
+ end
94
+ s
95
+ end
96
+
97
+ def default_config
98
+ super.merge(concurrency: 5)
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,45 @@
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/log'
19
+ require 'blender/handlers/base'
20
+
21
+ module Blender
22
+ class EventDispatcher
23
+ attr_reader :handlers
24
+ def initialize
25
+ @handlers = []
26
+ end
27
+
28
+ def register(handler)
29
+ @handlers << handler
30
+ end
31
+
32
+ # Define a method that will be forwarded to all
33
+ def self.def_forwarding_method(method_name)
34
+ class_eval(<<-END_OF_METHOD, __FILE__, __LINE__)
35
+ def #{method_name}(*args)
36
+ @handlers.each {|s| s.#{method_name}(*args)}
37
+ end
38
+ END_OF_METHOD
39
+ end
40
+
41
+ (Handlers::Base.instance_methods - Object.instance_methods).each do |method_name|
42
+ def_forwarding_method(method_name)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,26 @@
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
+ class ExecutionFailed < RuntimeError; end
20
+ class UnsupportedFeature < ArgumentError; end
21
+ class UnknownDriver < ArgumentError; end
22
+ class UnknownTask < ArgumentError; end
23
+ class UnknownSchedulingStrategy < ArgumentError; end
24
+ class MultipleDrivers < RuntimeError; end
25
+ class LockAcquisitionError < RuntimeError; end
26
+ end
@@ -0,0 +1,39 @@
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
+ module Handlers
20
+ class Base
21
+ def run_started(scheduler)
22
+ end
23
+ def run_finished(scheduler)
24
+ end
25
+ def run_failed(scheduler, e)
26
+ end
27
+ def job_computation_started(strategy)
28
+ end
29
+ def job_computation_finished(strategy, jobs)
30
+ end
31
+ def job_started(job)
32
+ end
33
+ def job_finished(job)
34
+ end
35
+ def job_failed(job, error)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,73 @@
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/utils/ui'
19
+ require 'blender/configuration'
20
+
21
+ module Blender
22
+ module Handlers
23
+ class Doc < Base
24
+
25
+ attr_reader :ui
26
+
27
+ def initialize
28
+ @ui = Blender::Utils::UI.new
29
+ end
30
+
31
+ def run_started(scheduler)
32
+ @start_time = Time.now
33
+ @task_id = 0
34
+ @job_id = 1
35
+ ui.puts_green("Run[#{scheduler.name}] started")
36
+ ui.puts_green('Running in No-Op mode, driver execution will be skipped') if Configuration[:noop]
37
+ end
38
+
39
+ def run_finished(scheduler)
40
+ delta = ( Time.now - @start_time)
41
+ ui.puts_green("Run finished (#{delta} s)")
42
+ end
43
+
44
+ def run_failed(scheduler, e)
45
+ delta = ( Time.now - @start_time)
46
+ ui.puts_red("Run failed (#{delta} s)")
47
+ ui.puts_red("Error :#{e.class} Message: #{e.message}")
48
+ ui.puts_red("Backtrace :#{e.backtrace.join("\n")}")
49
+ end
50
+
51
+ def job_started(job)
52
+ ui.puts(" #{job.to_s} started")
53
+ end
54
+
55
+ def job_finished(job)
56
+ end
57
+
58
+ def job_failed(job, e)
59
+ ui.puts_red(" #{job.to_s} failed")
60
+ end
61
+
62
+ def job_computation_started(strategy)
63
+ @compute_start_time = Time.now
64
+ @strategy = strategy.class.name.split('::').last
65
+ end
66
+
67
+ def job_computation_finished(scheduler, jobs)
68
+ delta = Time.now - @compute_start_time
69
+ ui.puts(" #{jobs.size} job(s) computed using '#{@strategy}' strategy")
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,73 @@
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/exceptions'
19
+ require 'blender/log'
20
+ require 'blender/configuration'
21
+
22
+ module Blender
23
+ # A job represent encapsulates an array of tasks to be performed
24
+ # against an array of hosts. Jobs are created by scheduling strategies,
25
+ # and passed to underlying drivers for execution
26
+ # Tasks within a single job must has exactly same driver.
27
+ class Job
28
+
29
+ attr_reader :tasks, :hosts, :driver, :id
30
+
31
+ # creates a new job
32
+ # @param id [Fixnum] a numeric identifier
33
+ # @param driver [Blender::Driver::Base] a driver object
34
+ # @patam hosts [Array] list of target hosts
35
+ # @patam hosts [Array] list of tasks to be run against the hosts
36
+ def initialize(id, driver, tasks, hosts)
37
+ @id = id
38
+ @tasks = Array(tasks)
39
+ @hosts = Array(hosts)
40
+ @driver = driver
41
+ end
42
+
43
+ def run
44
+ driver.execute(tasks, hosts) unless Configuration[:noop]
45
+ end
46
+
47
+ def to_s
48
+ "Job #{id} [#{name}]"
49
+ end
50
+
51
+ # computes, momoize and return the name of the job
52
+ # name is used to summarize the job.
53
+ # @return [String]
54
+ def name
55
+ @name ||= compute_name
56
+ end
57
+
58
+ private
59
+ def compute_name
60
+ if tasks.size == 1
61
+ t_part = tasks.first.name
62
+ else
63
+ t_part = "#{tasks.size} tasks"
64
+ end
65
+ if hosts.size == 1
66
+ h_part = hosts.first
67
+ else
68
+ h_part = "#{hosts.size} members"
69
+ end
70
+ "#{t_part} on #{h_part}"
71
+ end
72
+ end
73
+ end