pd-blender 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.rubocop.yml +2 -0
- data/.travis.yml +10 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +14 -0
- data/README.md +342 -0
- data/Rakefile +21 -0
- data/bin/blend +20 -0
- data/blender.gemspec +36 -0
- data/lib/blender.rb +67 -0
- data/lib/blender/cli.rb +71 -0
- data/lib/blender/configuration.rb +45 -0
- data/lib/blender/discovery.rb +41 -0
- data/lib/blender/drivers/base.rb +40 -0
- data/lib/blender/drivers/compound.rb +29 -0
- data/lib/blender/drivers/ruby.rb +55 -0
- data/lib/blender/drivers/shellout.rb +63 -0
- data/lib/blender/drivers/ssh.rb +93 -0
- data/lib/blender/drivers/ssh_multi.rb +102 -0
- data/lib/blender/event_dispatcher.rb +45 -0
- data/lib/blender/exceptions.rb +26 -0
- data/lib/blender/handlers/base.rb +39 -0
- data/lib/blender/handlers/doc.rb +73 -0
- data/lib/blender/job.rb +73 -0
- data/lib/blender/lock/flock.rb +64 -0
- data/lib/blender/log.rb +24 -0
- data/lib/blender/rspec.rb +68 -0
- data/lib/blender/rspec/stub_registry.rb +45 -0
- data/lib/blender/scheduled_job.rb +66 -0
- data/lib/blender/scheduler.rb +114 -0
- data/lib/blender/scheduler/dsl.rb +160 -0
- data/lib/blender/scheduling_strategies/base.rb +30 -0
- data/lib/blender/scheduling_strategies/default.rb +37 -0
- data/lib/blender/scheduling_strategies/per_host.rb +38 -0
- data/lib/blender/scheduling_strategies/per_task.rb +37 -0
- data/lib/blender/tasks/base.rb +72 -0
- data/lib/blender/tasks/ruby.rb +31 -0
- data/lib/blender/tasks/shell_out.rb +30 -0
- data/lib/blender/tasks/ssh.rb +25 -0
- data/lib/blender/timer.rb +54 -0
- data/lib/blender/utils/refinements.rb +45 -0
- data/lib/blender/utils/thread_pool.rb +54 -0
- data/lib/blender/utils/ui.rb +51 -0
- data/lib/blender/version.rb +20 -0
- data/spec/blender/blender_rspec.rb +31 -0
- data/spec/blender/discovery_spec.rb +16 -0
- data/spec/blender/drivers/ssh_multi_spec.rb +16 -0
- data/spec/blender/drivers/ssh_spec.rb +17 -0
- data/spec/blender/dsl_spec.rb +19 -0
- data/spec/blender/event_dispatcher_spec.rb +17 -0
- data/spec/blender/job_spec.rb +42 -0
- data/spec/blender/lock_spec.rb +129 -0
- data/spec/blender/scheduled_job_spec.rb +30 -0
- data/spec/blender/scheduler_spec.rb +140 -0
- data/spec/blender/scheduling_strategies/default_spec.rb +75 -0
- data/spec/blender/utils/refinements_spec.rb +16 -0
- data/spec/blender/utils/thread_pool_spec.rb +16 -0
- data/spec/blender_spec.rb +37 -0
- data/spec/data/example.rb +12 -0
- data/spec/spec_helper.rb +35 -0
- 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
|
data/lib/blender/job.rb
ADDED
@@ -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
|