pd-blender 0.0.1 → 0.1.0
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.
- checksums.yaml +4 -4
- data/.travis.yml +6 -2
- data/README.md +41 -7
- data/Rakefile +5 -3
- data/blender.gemspec +1 -0
- data/lib/blender.rb +9 -11
- data/lib/blender/cli.rb +15 -3
- data/lib/blender/configuration.rb +13 -2
- data/lib/blender/drivers/ruby.rb +0 -1
- data/lib/blender/drivers/scp.rb +85 -0
- data/lib/blender/drivers/shellout.rb +1 -4
- data/lib/blender/drivers/ssh.rb +5 -35
- data/lib/blender/drivers/ssh_exec.rb +54 -0
- data/lib/blender/drivers/ssh_multi.rb +3 -34
- data/lib/blender/event_dispatcher.rb +1 -1
- data/lib/blender/handlers/base.rb +7 -0
- data/lib/blender/handlers/doc.rb +0 -1
- data/lib/blender/job.rb +0 -1
- data/lib/blender/lock/flock.rb +3 -7
- data/lib/blender/rspec/stub_registry.rb +2 -0
- data/lib/blender/scheduler.rb +6 -5
- data/lib/blender/scheduler/dsl.rb +17 -0
- data/lib/blender/tasks/base.rb +0 -1
- data/lib/blender/tasks/ruby.rb +0 -1
- data/lib/blender/tasks/scp.rb +41 -0
- data/lib/blender/utils/refinements.rb +1 -1
- data/lib/blender/utils/thread_pool.rb +0 -1
- data/lib/blender/utils/ui.rb +1 -1
- data/lib/blender/version.rb +1 -1
- data/spec/blender/config_spec.rb +34 -0
- data/spec/blender/dsl_spec.rb +31 -0
- data/spec/blender/lock_spec.rb +51 -47
- data/spec/blender/scheduler_spec.rb +8 -0
- data/spec/blender_spec.rb +11 -0
- data/spec/data/example.rb +0 -1
- data/spec/spec_helper.rb +0 -10
- metadata +21 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4755355532133b48f4e4525b8a7671206318b470
|
|
4
|
+
data.tar.gz: 416b8fd419f08c1900b9f08cca300b7703d4ed68
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7822a210909825f27e67a8887083ac2a3521ef3d465efcdcd3e45dd7b61da3551df7ad59fe480dcbe9875ee6bc77261d3ced3a157b744055d02c4e6c336b08e3
|
|
7
|
+
data.tar.gz: 07c515f06d1ae9d67360e264c95e2be947cac421ca9319fefdbb1c08f26258a468466ff7976fbb9fff7d3bdc5467e28447fdaa5834858116c7edb85457a4d8b6
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# Blender
|
|
3
3
|
|
|
4
4
|
Blender is a modular remote command execution framework. Blender provides few basic
|
|
5
|
-
primitives to automate
|
|
5
|
+
primitives to automate across server workflows. Workflows can be expressed in plain
|
|
6
6
|
ruby DSL and executed using the CLI.
|
|
7
7
|
|
|
8
8
|
Following is an example of a simple blender script that will update the package
|
|
@@ -63,6 +63,17 @@ commands over ssh, serf handlers etc) which can ease automating large cluster
|
|
|
63
63
|
maintenance, multi stage provisioning, establishing cross server feedback
|
|
64
64
|
loops etc.
|
|
65
65
|
|
|
66
|
+
## Installation
|
|
67
|
+
|
|
68
|
+
Blender is published as `pd-blender` in rubygems. And you can install it as:
|
|
69
|
+
```sh
|
|
70
|
+
gem install pd-blender
|
|
71
|
+
```
|
|
72
|
+
Or declare it as a dependency in your Gemfile, if you are using bundler.
|
|
73
|
+
```ruby
|
|
74
|
+
gem 'pd-blender'
|
|
75
|
+
```
|
|
76
|
+
|
|
66
77
|
## Concepts
|
|
67
78
|
|
|
68
79
|
Blender is composed of two components:
|
|
@@ -71,7 +82,7 @@ Blender is composed of two components:
|
|
|
71
82
|
script can have multiple tasks. Tasks are executed using drivers. Tasks can declare their
|
|
72
83
|
target hosts.
|
|
73
84
|
|
|
74
|
-
* **Scheduling
|
|
85
|
+
* **Scheduling strategy** - Determines the order of task execution across the hosts.
|
|
75
86
|
Every blender scripts has one and only one scheduling strategy. Scheduling strategies
|
|
76
87
|
uses the task list as input and produces a list of jobs, to be executed using drivers.
|
|
77
88
|
|
|
@@ -115,6 +126,18 @@ can execute `ssh_task`s. Blender core ships with following tasks and drivers:
|
|
|
115
126
|
end
|
|
116
127
|
```
|
|
117
128
|
|
|
129
|
+
- **scp_task**: download or upload files using scp
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
members ['host1', 'host2', 'host3']
|
|
133
|
+
scp_upload '/foo/bar' do
|
|
134
|
+
from '/path/to/remote/file'
|
|
135
|
+
end
|
|
136
|
+
scp_download '/foo/bar' do
|
|
137
|
+
to '/local/path'
|
|
138
|
+
end
|
|
139
|
+
```
|
|
140
|
+
|
|
118
141
|
As mentioned earlier tasks are executed using drivers. Tasks can declare their preferred driver or
|
|
119
142
|
Blender will assign a driver to them automatically. Blender will reuse the global driver if its
|
|
120
143
|
compatible, else it will create one. By default the ```global_driver``` is a ```shell_out``` driver.
|
|
@@ -125,9 +148,9 @@ specific to their own implementations.
|
|
|
125
148
|
|
|
126
149
|
Scheduling strategies are the most crucial part of a blender script. They decide the
|
|
127
150
|
order of command execution across distributed nodes in blender. Each blender script is
|
|
128
|
-
|
|
151
|
+
invoked using one strategy. Consider them as a transformation, where the input is tasks and ouput is
|
|
129
152
|
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,
|
|
153
|
+
except a jobs can hold multiple tasks within them. We'll come to this later, but first, let's
|
|
131
154
|
see how the default strategy work.
|
|
132
155
|
|
|
133
156
|
- **default strategy**: the default strategy takes the list of declared tasks (and associated members
|
|
@@ -275,7 +298,7 @@ Following are some examples:
|
|
|
275
298
|
- **chef**: discover hosts using Chef search
|
|
276
299
|
|
|
277
300
|
```ruby
|
|
278
|
-
require 'blender/
|
|
301
|
+
require 'blender/discoveries/chef'
|
|
279
302
|
|
|
280
303
|
ruby_task 'print host name' do
|
|
281
304
|
execute do |host|
|
|
@@ -285,7 +308,7 @@ Following are some examples:
|
|
|
285
308
|
end
|
|
286
309
|
```
|
|
287
310
|
|
|
288
|
-
## Invoking blender periodially with Rufus
|
|
311
|
+
## Invoking blender periodially with Rufus scheduler
|
|
289
312
|
|
|
290
313
|
Blender is designed to be used as a standalone script that can be invoked on-demand or
|
|
291
314
|
consumed as a library, i.e. workflows are written in plain Ruby objects and invoked
|
|
@@ -320,7 +343,18 @@ both per task level as well as globally.
|
|
|
320
343
|
## Event handlers
|
|
321
344
|
|
|
322
345
|
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
|
|
346
|
+
be hooked into the event system (e.g. HipChat notification handlers, statsd handlers, etc) and blender
|
|
347
|
+
will automatically invoke them during key events. As of now, events are available before and after run
|
|
348
|
+
and per job execution. Event dispatch system is likely to get more elaborate and blender might have
|
|
349
|
+
few common event handlers (metric, notifications etc) in near future.
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
## Ancillary projects
|
|
353
|
+
|
|
354
|
+
Blender has a few ancillary projects for integration with other systems, following are few of them:
|
|
355
|
+
- Zookeeper based locking for distributed blender deployments [blender-zk](https://github.com/PagerDuty/blender-zk)
|
|
356
|
+
- Serf based host discovery and command dispatch [blender-serf](https://github.com/PagerDuty/blender-serf)
|
|
357
|
+
- Chef based host discovery [blender-chef](https://github.com/PagerDuty/blender-chef)
|
|
324
358
|
|
|
325
359
|
## Supported ruby versions
|
|
326
360
|
|
data/Rakefile
CHANGED
|
@@ -8,14 +8,16 @@ YARD::Rake::YardocTask.new do |t|
|
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
RSpec::Core::RakeTask.new(:spec) do |t|
|
|
11
|
-
t.pattern = %w
|
|
11
|
+
t.pattern = %w(spec/**/*_spec.rb)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
RSpec::Core::RakeTask.new(:rspec) do |t|
|
|
15
|
-
t.pattern = %w
|
|
15
|
+
t.pattern = %w(spec/**/*_rspec.rb)
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
RuboCop::RakeTask.new(:rubocop) do |t|
|
|
19
|
-
t.patterns = %w
|
|
19
|
+
t.patterns = %w(Rakefile Gemfile lib/**/*.rb)
|
|
20
20
|
t.fail_on_error = true
|
|
21
21
|
end
|
|
22
|
+
|
|
23
|
+
task default: [:spec, :rubocop]
|
data/blender.gemspec
CHANGED
|
@@ -24,6 +24,7 @@ Gem::Specification.new do |spec|
|
|
|
24
24
|
spec.add_dependency 'mixlib-shellout'
|
|
25
25
|
spec.add_dependency 'mixlib-log'
|
|
26
26
|
spec.add_dependency 'net-ssh'
|
|
27
|
+
spec.add_dependency 'net-scp'
|
|
27
28
|
spec.add_dependency 'rufus-scheduler'
|
|
28
29
|
|
|
29
30
|
spec.add_development_dependency 'bundler'
|
data/lib/blender.rb
CHANGED
|
@@ -29,13 +29,13 @@ module Blender
|
|
|
29
29
|
# command and executed with local shellout driver
|
|
30
30
|
#
|
|
31
31
|
# @param name [String] Name of the run
|
|
32
|
+
# @param options[Hash] Additional options for scheduler
|
|
32
33
|
#
|
|
33
34
|
# @return [void]
|
|
34
|
-
def self.blend(name,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
end
|
|
35
|
+
def self.blend(name, options = {})
|
|
36
|
+
config_file = options.delete(:config_file)
|
|
37
|
+
scheduler = Scheduler.new(name, [], options)
|
|
38
|
+
configure(config_file) if config_file
|
|
39
39
|
if block_given?
|
|
40
40
|
yield scheduler
|
|
41
41
|
else
|
|
@@ -47,12 +47,10 @@ module Blender
|
|
|
47
47
|
|
|
48
48
|
def self.configure(file)
|
|
49
49
|
data = JSON.parse(File.read(file))
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
Blender::Log.init(data['log_file'])
|
|
55
|
-
end
|
|
50
|
+
|
|
51
|
+
Blender::Log.level = data['log_level'].to_sym if data['log_level']
|
|
52
|
+
Blender::Log.init(data['log_file']) if data['log_file']
|
|
53
|
+
|
|
56
54
|
if data['load_paths']
|
|
57
55
|
data['load_paths'].each do |path|
|
|
58
56
|
$LOAD_PATH.unshift(path)
|
data/lib/blender/cli.rb
CHANGED
|
@@ -21,10 +21,11 @@ require 'blender/timer'
|
|
|
21
21
|
|
|
22
22
|
module Blender
|
|
23
23
|
class CLI < Thor
|
|
24
|
-
|
|
25
24
|
def self.exit_on_failure?
|
|
26
25
|
true
|
|
27
26
|
end
|
|
27
|
+
stop_on_unknown_option! :from_file
|
|
28
|
+
check_unknown_options! except: :from_file
|
|
28
29
|
|
|
29
30
|
default_command :from_file
|
|
30
31
|
package_name 'Blender'
|
|
@@ -47,11 +48,22 @@ module Blender
|
|
|
47
48
|
aliases: '-n',
|
|
48
49
|
banner: 'No-op mode, run blender without executing jobs'
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
method_option :quiet,
|
|
52
|
+
default: false,
|
|
53
|
+
type: :boolean,
|
|
54
|
+
aliases: '-q',
|
|
55
|
+
banner: 'Quiet mode. Disable printing running job details'
|
|
56
|
+
|
|
57
|
+
def from_file(*args)
|
|
51
58
|
Configuration[:noop] = options[:noop]
|
|
59
|
+
Configuration[:arguments] = args
|
|
52
60
|
des = File.read(options[:file])
|
|
53
61
|
$LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(options[:file]), 'lib')))
|
|
54
|
-
|
|
62
|
+
scheduler_options = {
|
|
63
|
+
config_file: options[:config_file],
|
|
64
|
+
no_doc: options[:quiet]
|
|
65
|
+
}
|
|
66
|
+
Blender.blend(options[:file], scheduler_options) do |sch|
|
|
55
67
|
sch.instance_eval(des, __FILE__, __LINE__)
|
|
56
68
|
end
|
|
57
69
|
end
|
|
@@ -25,9 +25,8 @@ module Blender
|
|
|
25
25
|
attr_reader :data, :mutex
|
|
26
26
|
|
|
27
27
|
def initialize
|
|
28
|
-
@data = Hash.new{|h,k| h[k] = Hash.new}
|
|
29
|
-
@data[:noop] = false
|
|
30
28
|
@mutex = Mutex.new
|
|
29
|
+
reset!
|
|
31
30
|
end
|
|
32
31
|
|
|
33
32
|
def self.[]=(key, value)
|
|
@@ -41,5 +40,17 @@ module Blender
|
|
|
41
40
|
instance.data[key]
|
|
42
41
|
end
|
|
43
42
|
end
|
|
43
|
+
|
|
44
|
+
def self.reset!
|
|
45
|
+
instance.mutex.synchronize do
|
|
46
|
+
instance.reset!
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def reset!
|
|
51
|
+
@data = Hash.new{|h, k| h[k] = Hash.new}
|
|
52
|
+
@data[:noop] = false
|
|
53
|
+
@data[:arguments] = []
|
|
54
|
+
end
|
|
44
55
|
end
|
|
45
56
|
end
|
data/lib/blender/drivers/ruby.rb
CHANGED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Author:: Ranjib Dey (<ranjib@pagerduty.com>)
|
|
3
|
+
# Copyright:: Copyright (c) 2015 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/scp'
|
|
19
|
+
require 'net/ssh'
|
|
20
|
+
require 'blender/exceptions'
|
|
21
|
+
require 'blender/drivers/base'
|
|
22
|
+
|
|
23
|
+
module Blender
|
|
24
|
+
module Driver
|
|
25
|
+
class Scp < Base
|
|
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("SCP execution tasks [#{Array(tasks).size}]")
|
|
36
|
+
Log.debug("SCP on hosts [#{hosts.join(",")}]")
|
|
37
|
+
Array(hosts).each do |host|
|
|
38
|
+
session = create_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
|
+
begin
|
|
51
|
+
case command.direction
|
|
52
|
+
when :upload
|
|
53
|
+
session.scp.upload!(command.source, command.target)
|
|
54
|
+
ExecOutput.new(0, '', '')
|
|
55
|
+
when :download
|
|
56
|
+
session.scp.download!(command.source, command.target)
|
|
57
|
+
ExecOutput.new(0, '', '')
|
|
58
|
+
else
|
|
59
|
+
ExecOutput.new(-1, '' , "Invalid direction. Can be either :upload or :download. Found:'#{command.direction}'")
|
|
60
|
+
end
|
|
61
|
+
rescue StandardError => e
|
|
62
|
+
ExecOutput.new(-1, stdout, e.message + e.backtrace.join("\n"))
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def create_session(host)
|
|
69
|
+
Log.debug("Invoking ssh: #{user}@#{host}")
|
|
70
|
+
Net::SSH.start(host, user, config)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
class ScpUpload < Blender::Driver::Scp
|
|
75
|
+
end
|
|
76
|
+
class ScpDownload < Blender::Driver::Scp
|
|
77
|
+
def run_command(command, session)
|
|
78
|
+
begin
|
|
79
|
+
rescue StandardError => e
|
|
80
|
+
ExecOutput.new(-1, stdout, e.message + e.backtrace.join("\n"))
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -20,14 +20,11 @@ require 'mixlib/shellout'
|
|
|
20
20
|
module Blender
|
|
21
21
|
module Driver
|
|
22
22
|
class ShellOut < Base
|
|
23
|
-
|
|
24
23
|
def initialize(config = {})
|
|
25
24
|
@options = {}
|
|
26
25
|
cfg = config.dup
|
|
27
26
|
[:user, :group, :cwd, :umask, :returns, :environment, :timeout].each do |key|
|
|
28
|
-
if cfg.key?(key)
|
|
29
|
-
@options[key] = cfg.delete(key)
|
|
30
|
-
end
|
|
27
|
+
@options[key] = cfg.delete(key) if cfg.key?(key)
|
|
31
28
|
end
|
|
32
29
|
super(cfg)
|
|
33
30
|
end
|
data/lib/blender/drivers/ssh.rb
CHANGED
|
@@ -18,12 +18,13 @@
|
|
|
18
18
|
require 'net/ssh'
|
|
19
19
|
require 'blender/exceptions'
|
|
20
20
|
require 'blender/drivers/base'
|
|
21
|
+
require 'blender/drivers/ssh_exec'
|
|
21
22
|
|
|
22
23
|
module Blender
|
|
23
24
|
module Driver
|
|
24
25
|
class Ssh < Base
|
|
25
|
-
|
|
26
26
|
attr_reader :user
|
|
27
|
+
include SSHExec
|
|
27
28
|
|
|
28
29
|
def initialize(config = {})
|
|
29
30
|
cfg = config.dup
|
|
@@ -35,7 +36,7 @@ module Blender
|
|
|
35
36
|
Log.debug("SSH execution tasks [#{Array(tasks).size}]")
|
|
36
37
|
Log.debug("SSH on hosts [#{hosts.join(",")}]")
|
|
37
38
|
Array(hosts).each do |host|
|
|
38
|
-
session =
|
|
39
|
+
session = create_session(host)
|
|
39
40
|
Array(tasks).each do |task|
|
|
40
41
|
cmd = run_command(task.command, session)
|
|
41
42
|
if cmd.exitstatus != 0 and !task.metadata[:ignore_failure]
|
|
@@ -47,47 +48,16 @@ module Blender
|
|
|
47
48
|
end
|
|
48
49
|
|
|
49
50
|
def run_command(command, session)
|
|
50
|
-
|
|
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
|
|
51
|
+
exit_status = remote_exec(command, session)
|
|
78
52
|
ExecOutput.new(exit_status, stdout, stderr)
|
|
79
53
|
end
|
|
80
54
|
|
|
81
55
|
private
|
|
82
56
|
|
|
83
|
-
def
|
|
57
|
+
def create_session(host)
|
|
84
58
|
Log.debug("Invoking ssh: #{user}@#{host}")
|
|
85
59
|
Net::SSH.start(host, user, config)
|
|
86
60
|
end
|
|
87
|
-
|
|
88
|
-
def fixup_sudo(command)
|
|
89
|
-
command.sub(/^sudo/, 'sudo -p \'blender sudo password: \'')
|
|
90
|
-
end
|
|
91
61
|
end
|
|
92
62
|
end
|
|
93
63
|
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Author:: Ranjib Dey (<ranjib@pagerduty.com>)
|
|
3
|
+
# Author:: Smit Shah (<who828@gmail.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/log'
|
|
20
|
+
module Blender::Driver::SSHExec
|
|
21
|
+
def remote_exec(command, session)
|
|
22
|
+
password = config[:password]
|
|
23
|
+
command = fixup_sudo(command)
|
|
24
|
+
exit_status = 0
|
|
25
|
+
channel = session.open_channel do |ch|
|
|
26
|
+
ch.request_pty
|
|
27
|
+
ch.exec(command) do |ch, success|
|
|
28
|
+
unless success
|
|
29
|
+
Blender::Log.debug("Command not found:#{success.inspect}")
|
|
30
|
+
exit_status = -1
|
|
31
|
+
end
|
|
32
|
+
ch.on_data do |c, data|
|
|
33
|
+
stdout << data
|
|
34
|
+
c.send_data("#{password}\n") if data =~ /^blender sudo password: /
|
|
35
|
+
end
|
|
36
|
+
ch.on_extended_data do |c, type, data|
|
|
37
|
+
stderr << data
|
|
38
|
+
end
|
|
39
|
+
ch.on_request "exit-status" do |ichannel, data|
|
|
40
|
+
l = data.read_long
|
|
41
|
+
exit_status = [exit_status, l].max
|
|
42
|
+
Blender::Log.debug("exit_status:#{exit_status} , data:#{l}")
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
Blender::Log.debug("Exit(#{exit_status}) Command: '#{command}'")
|
|
46
|
+
end
|
|
47
|
+
channel.wait
|
|
48
|
+
exit_status
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def fixup_sudo(command)
|
|
52
|
+
command.sub(/^sudo/, 'sudo -p \'blender sudo password: \'')
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
require 'net/ssh'
|
|
19
19
|
require 'blender/exceptions'
|
|
20
20
|
require 'blender/drivers/ssh'
|
|
21
|
+
require 'blender/drivers/ssh_exec'
|
|
21
22
|
|
|
22
23
|
module Blender
|
|
23
24
|
module Driver
|
|
@@ -26,7 +27,7 @@ module Blender
|
|
|
26
27
|
def execute(tasks, hosts)
|
|
27
28
|
Log.debug("SSH execution tasks [#{tasks.size}]")
|
|
28
29
|
Log.debug("SSH on hosts [#{hosts.join("\n")}]")
|
|
29
|
-
session =
|
|
30
|
+
session = create_session(hosts)
|
|
30
31
|
Array(tasks).each do |task|
|
|
31
32
|
cmd = run_command(task.command, session)
|
|
32
33
|
if cmd.exitstatus != 0 and !task.metadata[:ignore_failure]
|
|
@@ -36,45 +37,13 @@ module Blender
|
|
|
36
37
|
session.loop
|
|
37
38
|
end
|
|
38
39
|
|
|
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
40
|
def concurrency
|
|
72
41
|
@config[:concurrency]
|
|
73
42
|
end
|
|
74
43
|
|
|
75
44
|
private
|
|
76
45
|
|
|
77
|
-
def
|
|
46
|
+
def create_session(hosts)
|
|
78
47
|
user = @config[:user] || ENV['USER']
|
|
79
48
|
ssh_config = { password: @config[:password]}
|
|
80
49
|
error_handler = lambda do |server|
|
|
@@ -20,18 +20,25 @@ module Blender
|
|
|
20
20
|
class Base
|
|
21
21
|
def run_started(scheduler)
|
|
22
22
|
end
|
|
23
|
+
|
|
23
24
|
def run_finished(scheduler)
|
|
24
25
|
end
|
|
26
|
+
|
|
25
27
|
def run_failed(scheduler, e)
|
|
26
28
|
end
|
|
29
|
+
|
|
27
30
|
def job_computation_started(strategy)
|
|
28
31
|
end
|
|
32
|
+
|
|
29
33
|
def job_computation_finished(strategy, jobs)
|
|
30
34
|
end
|
|
35
|
+
|
|
31
36
|
def job_started(job)
|
|
32
37
|
end
|
|
38
|
+
|
|
33
39
|
def job_finished(job)
|
|
34
40
|
end
|
|
41
|
+
|
|
35
42
|
def job_failed(job, error)
|
|
36
43
|
end
|
|
37
44
|
end
|
data/lib/blender/handlers/doc.rb
CHANGED
data/lib/blender/job.rb
CHANGED
data/lib/blender/lock/flock.rb
CHANGED
|
@@ -21,12 +21,10 @@ require 'fcntl'
|
|
|
21
21
|
module Blender
|
|
22
22
|
module Lock
|
|
23
23
|
class Flock
|
|
24
|
-
|
|
25
24
|
def initialize(name, options)
|
|
26
25
|
@path = options['path'] || File.join('/tmp', name)
|
|
27
26
|
@timeout = options[:timeout] || 0
|
|
28
27
|
@job_name = name
|
|
29
|
-
@lock_fd = nil
|
|
30
28
|
end
|
|
31
29
|
|
|
32
30
|
def acquire
|
|
@@ -38,15 +36,15 @@ module Blender
|
|
|
38
36
|
end
|
|
39
37
|
else
|
|
40
38
|
locked = @lock_fd.flock(File::LOCK_NB | File::LOCK_EX)
|
|
41
|
-
raise LockAcquisitionError,
|
|
39
|
+
raise LockAcquisitionError, "Failed to lock file '#{@path}'" if locked == false
|
|
42
40
|
end
|
|
43
41
|
@lock_fd.write({job: @job_name, pid: Process.pid }.inspect)
|
|
44
42
|
end
|
|
45
43
|
|
|
46
44
|
def release
|
|
47
|
-
@lock_fd
|
|
48
45
|
@lock_fd.flock(File::LOCK_UN)
|
|
49
46
|
@lock_fd.close
|
|
47
|
+
File.delete(@path) if File.exists?(@path)
|
|
50
48
|
end
|
|
51
49
|
|
|
52
50
|
def with_lock
|
|
@@ -54,10 +52,8 @@ module Blender
|
|
|
54
52
|
yield if block_given?
|
|
55
53
|
rescue Timeout::Error => e
|
|
56
54
|
raise LockAcquisitionError, 'Timeout while waiting for lock acquisition'
|
|
57
|
-
rescue LockAcquisitionError => e
|
|
58
|
-
raise e
|
|
59
55
|
ensure
|
|
60
|
-
release
|
|
56
|
+
release if @lock_fd
|
|
61
57
|
end
|
|
62
58
|
end
|
|
63
59
|
end
|
|
@@ -25,6 +25,7 @@ module Blender
|
|
|
25
25
|
@type = type
|
|
26
26
|
@opts = opts
|
|
27
27
|
end
|
|
28
|
+
|
|
28
29
|
def and_return(value)
|
|
29
30
|
@return_value = value
|
|
30
31
|
end
|
|
@@ -35,6 +36,7 @@ module Blender
|
|
|
35
36
|
def initialize
|
|
36
37
|
@data = []
|
|
37
38
|
end
|
|
39
|
+
|
|
38
40
|
def self.add(type, opts)
|
|
39
41
|
obj = SearchStub.new(type, opts)
|
|
40
42
|
instance.data << obj
|
data/lib/blender/scheduler.rb
CHANGED
|
@@ -30,7 +30,6 @@ require 'blender/tasks/base'
|
|
|
30
30
|
|
|
31
31
|
module Blender
|
|
32
32
|
class Scheduler
|
|
33
|
-
|
|
34
33
|
include SchedulerDSL
|
|
35
34
|
include Lock
|
|
36
35
|
|
|
@@ -39,14 +38,16 @@ module Blender
|
|
|
39
38
|
attr_reader :events, :tasks
|
|
40
39
|
attr_reader :lock_properties
|
|
41
40
|
|
|
42
|
-
def initialize(name, tasks = [],
|
|
41
|
+
def initialize(name, tasks = [], options = {})
|
|
43
42
|
@name = name
|
|
44
43
|
@tasks = tasks
|
|
45
|
-
@metadata = default_metadata.merge(metadata)
|
|
46
44
|
@events = Blender::EventDispatcher.new
|
|
47
|
-
|
|
45
|
+
unless options.delete(:no_doc)
|
|
46
|
+
events.register(Blender::Handlers::Doc.new)
|
|
47
|
+
end
|
|
48
|
+
@metadata = default_metadata.merge(options)
|
|
48
49
|
@scheduling_strategy = nil
|
|
49
|
-
@lock_properties = {driver:
|
|
50
|
+
@lock_properties = {driver: nil, driver_options: {}}
|
|
50
51
|
end
|
|
51
52
|
|
|
52
53
|
def run
|
|
@@ -21,12 +21,15 @@ require 'blender/tasks/base'
|
|
|
21
21
|
require 'blender/tasks/ruby'
|
|
22
22
|
require 'blender/tasks/ssh'
|
|
23
23
|
require 'blender/tasks/shell_out'
|
|
24
|
+
require 'blender/tasks/scp'
|
|
24
25
|
require 'highline'
|
|
25
26
|
require 'blender/utils/refinements'
|
|
26
27
|
require 'blender/drivers/ssh'
|
|
28
|
+
require 'blender/drivers/ssh'
|
|
27
29
|
require 'blender/drivers/ssh_multi'
|
|
28
30
|
require 'blender/drivers/shellout'
|
|
29
31
|
require 'blender/drivers/ruby'
|
|
32
|
+
require 'blender/drivers/scp'
|
|
30
33
|
require 'blender/discovery'
|
|
31
34
|
require 'blender/handlers/base'
|
|
32
35
|
require 'blender/lock/flock'
|
|
@@ -116,6 +119,20 @@ module Blender
|
|
|
116
119
|
append_task(:ssh, task)
|
|
117
120
|
end
|
|
118
121
|
|
|
122
|
+
def scp_upload(name, &block)
|
|
123
|
+
task = build_task(name, :scp)
|
|
124
|
+
task.instance_eval(&block) if block_given?
|
|
125
|
+
task.direction = :upload
|
|
126
|
+
append_task(:scp, task)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def scp_download(name, &block)
|
|
130
|
+
task = build_task(name, :scp)
|
|
131
|
+
task.instance_eval(&block) if block_given?
|
|
132
|
+
task.direction = :download
|
|
133
|
+
append_task(:scp, task)
|
|
134
|
+
end
|
|
135
|
+
|
|
119
136
|
def strategy(strategy)
|
|
120
137
|
klass_name = camelcase(strategy.to_s).to_sym
|
|
121
138
|
begin
|
data/lib/blender/tasks/base.rb
CHANGED
data/lib/blender/tasks/ruby.rb
CHANGED
|
@@ -0,0 +1,41 @@
|
|
|
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/tasks/ssh'
|
|
19
|
+
require 'forwardable'
|
|
20
|
+
|
|
21
|
+
module Blender
|
|
22
|
+
module Task
|
|
23
|
+
class Scp < Blender::Task::Base
|
|
24
|
+
extend Forwardable
|
|
25
|
+
def_delegators :@command, :direction, :direction=
|
|
26
|
+
def initialize(name, metadata = {})
|
|
27
|
+
super
|
|
28
|
+
@command = Struct.new(:direction, :source, :target).new
|
|
29
|
+
@command.target = name
|
|
30
|
+
@command.source = name
|
|
31
|
+
@direction = :upload
|
|
32
|
+
end
|
|
33
|
+
def from(source)
|
|
34
|
+
@command.source = source
|
|
35
|
+
end
|
|
36
|
+
def to(target)
|
|
37
|
+
@command.target = target
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
data/lib/blender/utils/ui.rb
CHANGED
|
@@ -24,6 +24,7 @@ module Blender
|
|
|
24
24
|
@mutex = Mutex.new
|
|
25
25
|
@highline = HighLine.new
|
|
26
26
|
end
|
|
27
|
+
|
|
27
28
|
def puts(string)
|
|
28
29
|
@mutex.synchronize do
|
|
29
30
|
$stdout.puts(string)
|
|
@@ -45,7 +46,6 @@ module Blender
|
|
|
45
46
|
def color(string, *colors)
|
|
46
47
|
@highline.color(string, *colors)
|
|
47
48
|
end
|
|
48
|
-
|
|
49
49
|
end
|
|
50
50
|
end
|
|
51
51
|
end
|
data/lib/blender/version.rb
CHANGED
|
@@ -0,0 +1,34 @@
|
|
|
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 'spec_helper'
|
|
19
|
+
|
|
20
|
+
describe Blender::Configuration do
|
|
21
|
+
it 'should populate argments and noop key' do
|
|
22
|
+
expect(Blender::Configuration[:noop]).to be(false)
|
|
23
|
+
expect(Blender::Configuration[:argments]).to be_empty
|
|
24
|
+
end
|
|
25
|
+
it 'should set configs globally' do
|
|
26
|
+
Blender::Configuration[:x] = 1
|
|
27
|
+
expect(Blender::Configuration[:x]).to eq(1)
|
|
28
|
+
end
|
|
29
|
+
it 'should reset config' do
|
|
30
|
+
Blender::Configuration[:x] = 1
|
|
31
|
+
Blender::Configuration.reset!
|
|
32
|
+
expect(Blender::Configuration[:x]).to be_empty
|
|
33
|
+
end
|
|
34
|
+
end
|
data/spec/blender/dsl_spec.rb
CHANGED
|
@@ -16,4 +16,35 @@ describe '#dsl' do
|
|
|
16
16
|
allow_any_instance_of(Blender::Driver::Ssh).to receive(:execute)
|
|
17
17
|
scheduler.run
|
|
18
18
|
end
|
|
19
|
+
|
|
20
|
+
context '#scp' do
|
|
21
|
+
it '#upload' do
|
|
22
|
+
session = double('Net::SSH::Session', loop: true)
|
|
23
|
+
scp = double('Net::SSH::Scp')
|
|
24
|
+
expect(Net::SSH).to receive(:start).with('host1', 'x', password: 'y').and_return(session)
|
|
25
|
+
expect(session).to receive(:scp).and_return(scp)
|
|
26
|
+
expect(scp).to receive(:upload!).with('/local/path', '/remote/path')
|
|
27
|
+
Blender.blend('test') do |sched|
|
|
28
|
+
sched.config(:scp, user: 'x', password: 'y')
|
|
29
|
+
sched.members(['host1'])
|
|
30
|
+
sched.scp_upload '/remote/path' do
|
|
31
|
+
from '/local/path'
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
it '#download' do
|
|
36
|
+
session = double('Net::SSH::Session', loop: true)
|
|
37
|
+
scp = double('Net::SSH::Scp')
|
|
38
|
+
expect(Net::SSH).to receive(:start).with('host1', 'x', password: 'y').and_return(session)
|
|
39
|
+
expect(session).to receive(:scp).and_return(scp)
|
|
40
|
+
expect(scp).to receive(:download!).with('/remote/path', '/local/path')
|
|
41
|
+
Blender.blend('test') do |sched|
|
|
42
|
+
sched.members(['host1'])
|
|
43
|
+
sched.config(:scp, user: 'x', password: 'y')
|
|
44
|
+
sched.scp_download '/remote/path' do
|
|
45
|
+
to '/local/path'
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
19
50
|
end
|
data/spec/blender/lock_spec.rb
CHANGED
|
@@ -1,72 +1,75 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
|
|
3
3
|
describe Blender::Lock do
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
4
|
+
context 'File based locking' do
|
|
5
|
+
it 'should not allow two blender run with same lockfile to run at the same time' do
|
|
6
|
+
pid1 = fork do
|
|
7
|
+
Blender.blend('test-1') do |sched|
|
|
8
|
+
sched.lock_options('flock')
|
|
9
|
+
sched.members(['localhost'])
|
|
10
|
+
sched.ruby_task('date') do
|
|
11
|
+
execute do
|
|
12
|
+
sleep 5
|
|
13
|
+
puts 'This will succeed'
|
|
14
|
+
end
|
|
14
15
|
end
|
|
15
16
|
end
|
|
16
17
|
end
|
|
17
|
-
end
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
pid2 = fork do
|
|
20
|
+
STDERR.reopen(File::NULL)
|
|
21
|
+
Blender.blend('test-1') do |sched|
|
|
22
|
+
sched.lock_options('flock')
|
|
23
|
+
sched.members(['localhost'])
|
|
24
|
+
sched.ruby_task('date') do
|
|
25
|
+
execute do
|
|
26
|
+
puts 'This will fail'
|
|
27
|
+
end
|
|
26
28
|
end
|
|
27
29
|
end
|
|
28
30
|
end
|
|
31
|
+
|
|
32
|
+
status1 = Process.wait2 pid1
|
|
33
|
+
status2 = Process.wait2 pid2
|
|
34
|
+
expect(status1.last.exitstatus).to eq(0)
|
|
35
|
+
expect(status2.last.exitstatus).to_not eq(0)
|
|
29
36
|
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
37
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
it 'should allow two blender run with different lock file to run at the same time' do
|
|
39
|
+
pid1 = fork do
|
|
40
|
+
Blender.blend('test-1') do |sched|
|
|
41
|
+
sched.lock_options('flock')
|
|
42
|
+
sched.members(['localhost'])
|
|
43
|
+
sched.ruby_task('date') do
|
|
44
|
+
execute do
|
|
45
|
+
sleep 5
|
|
46
|
+
puts 'This will succeed'
|
|
47
|
+
end
|
|
44
48
|
end
|
|
45
49
|
end
|
|
46
50
|
end
|
|
47
|
-
end
|
|
48
51
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
pid2 = fork do
|
|
53
|
+
Blender.blend('test-2') do |sched|
|
|
54
|
+
sched.lock_options('flock')
|
|
55
|
+
sched.members(['localhost'])
|
|
56
|
+
sched.ruby_task('date') do
|
|
57
|
+
execute do
|
|
58
|
+
puts 'This will succeed'
|
|
59
|
+
end
|
|
55
60
|
end
|
|
56
61
|
end
|
|
57
62
|
end
|
|
63
|
+
status1 = Process.wait2 pid1
|
|
64
|
+
status2 = Process.wait2 pid2
|
|
65
|
+
expect(status1.last.exitstatus).to eq(0)
|
|
66
|
+
expect(status2.last.exitstatus).to eq(0)
|
|
58
67
|
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
68
|
|
|
67
69
|
it 'should raise lock acquisition error when times out' do
|
|
68
70
|
pid1 = fork do
|
|
69
71
|
Blender.blend('test-1') do |sched|
|
|
72
|
+
sched.lock_options('flock')
|
|
70
73
|
sched.members(['localhost'])
|
|
71
74
|
sched.ruby_task('date') do
|
|
72
75
|
execute do
|
|
@@ -98,6 +101,7 @@ describe Blender::Lock do
|
|
|
98
101
|
it 'should not raise lock acquisition error when able to acquire lock within timeout period' do
|
|
99
102
|
pid1 = fork do
|
|
100
103
|
Blender.blend('test-1') do |sched|
|
|
104
|
+
sched.lock_options('flock')
|
|
101
105
|
sched.members(['localhost'])
|
|
102
106
|
sched.ruby_task('date') do
|
|
103
107
|
execute do
|
|
@@ -112,7 +116,7 @@ describe Blender::Lock do
|
|
|
112
116
|
STDERR.reopen(File::NULL)
|
|
113
117
|
Blender.blend('test-1') do |sched|
|
|
114
118
|
sched.members(['localhost'])
|
|
115
|
-
sched.lock_options('flock', timeout:
|
|
119
|
+
sched.lock_options('flock', timeout: 10)
|
|
116
120
|
sched.ruby_task('date') do
|
|
117
121
|
execute do
|
|
118
122
|
puts 'This will fail'
|
|
@@ -123,7 +127,7 @@ describe Blender::Lock do
|
|
|
123
127
|
status1 = Process.wait2 pid1
|
|
124
128
|
status2 = Process.wait2 pid2
|
|
125
129
|
expect(status1.last.exitstatus).to eq(0)
|
|
126
|
-
expect(status2.last.exitstatus).
|
|
130
|
+
expect(status2.last.exitstatus).to eq(0)
|
|
127
131
|
end
|
|
128
132
|
end
|
|
129
133
|
end
|
|
@@ -4,6 +4,14 @@ describe Blender::Scheduler do
|
|
|
4
4
|
let(:scheduler) do
|
|
5
5
|
described_class.new('test')
|
|
6
6
|
end
|
|
7
|
+
describe '#no_doc' do
|
|
8
|
+
it 'should not use document handler if no_doc option is passed' do
|
|
9
|
+
expect(Blender::Handlers::Doc).to_not receive(:new)
|
|
10
|
+
task = Blender::Task::Ruby.new('test')
|
|
11
|
+
sched = described_class.new('no_doc', [] , no_doc: true)
|
|
12
|
+
sched.run
|
|
13
|
+
end
|
|
14
|
+
end
|
|
7
15
|
describe '#DSL' do
|
|
8
16
|
subject(:task){scheduler.tasks.first}
|
|
9
17
|
it '#ask' do
|
data/spec/blender_spec.rb
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
# limitations under the License.
|
|
17
17
|
|
|
18
18
|
require 'spec_helper'
|
|
19
|
+
require 'blender/cli'
|
|
19
20
|
|
|
20
21
|
describe Blender do
|
|
21
22
|
describe '#blend' do
|
|
@@ -33,5 +34,15 @@ describe Blender do
|
|
|
33
34
|
end
|
|
34
35
|
expect(x).to eq(100)
|
|
35
36
|
end
|
|
37
|
+
context 'CLI' do
|
|
38
|
+
it 'should store additional arguments in config' do
|
|
39
|
+
Blender::CLI.start(%w{-f spec/data/example.rb -x -y -z})
|
|
40
|
+
expect(Blender::Configuration[:arguments]).to eq(%w{-x -y -z})
|
|
41
|
+
end
|
|
42
|
+
it 'should store additional arguments in config' do
|
|
43
|
+
expect(Blender::Handlers::Doc).to_not receive(:new)
|
|
44
|
+
Blender::CLI.start(%w{-q -f spec/data/example.rb})
|
|
45
|
+
end
|
|
46
|
+
end
|
|
36
47
|
end
|
|
37
48
|
end
|
data/spec/data/example.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
require 'simplecov'
|
|
2
|
-
SimpleCov.formatter = SimpleCov::Formatter::HTMLFormatter
|
|
3
|
-
SimpleCov.start do
|
|
4
|
-
add_filter '/spec/'
|
|
5
|
-
add_filter '.bundle'
|
|
6
|
-
end
|
|
7
1
|
|
|
8
2
|
require 'rspec'
|
|
9
3
|
require 'rspec/mocks'
|
|
@@ -27,9 +21,5 @@ RSpec.configure do |config|
|
|
|
27
21
|
config.mock_with :rspec do |mocks|
|
|
28
22
|
mocks.verify_doubled_constant_names = true
|
|
29
23
|
end
|
|
30
|
-
config.before(:each) do
|
|
31
|
-
doc = double(Blender::Handlers::Doc).as_null_object
|
|
32
|
-
allow(Blender::Handlers::Doc).to receive(:new).and_return(doc)
|
|
33
|
-
end
|
|
34
24
|
config.backtrace_exclusion_patterns = []
|
|
35
25
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pd-blender
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0
|
|
4
|
+
version: 0.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ranjib Dey
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2015-02-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: highline
|
|
@@ -80,6 +80,20 @@ dependencies:
|
|
|
80
80
|
- - ">="
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
82
|
version: '0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: net-scp
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0'
|
|
90
|
+
type: :runtime
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ">="
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '0'
|
|
83
97
|
- !ruby/object:Gem::Dependency
|
|
84
98
|
name: rufus-scheduler
|
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -216,8 +230,10 @@ files:
|
|
|
216
230
|
- lib/blender/drivers/base.rb
|
|
217
231
|
- lib/blender/drivers/compound.rb
|
|
218
232
|
- lib/blender/drivers/ruby.rb
|
|
233
|
+
- lib/blender/drivers/scp.rb
|
|
219
234
|
- lib/blender/drivers/shellout.rb
|
|
220
235
|
- lib/blender/drivers/ssh.rb
|
|
236
|
+
- lib/blender/drivers/ssh_exec.rb
|
|
221
237
|
- lib/blender/drivers/ssh_multi.rb
|
|
222
238
|
- lib/blender/event_dispatcher.rb
|
|
223
239
|
- lib/blender/exceptions.rb
|
|
@@ -237,6 +253,7 @@ files:
|
|
|
237
253
|
- lib/blender/scheduling_strategies/per_task.rb
|
|
238
254
|
- lib/blender/tasks/base.rb
|
|
239
255
|
- lib/blender/tasks/ruby.rb
|
|
256
|
+
- lib/blender/tasks/scp.rb
|
|
240
257
|
- lib/blender/tasks/shell_out.rb
|
|
241
258
|
- lib/blender/tasks/ssh.rb
|
|
242
259
|
- lib/blender/timer.rb
|
|
@@ -245,6 +262,7 @@ files:
|
|
|
245
262
|
- lib/blender/utils/ui.rb
|
|
246
263
|
- lib/blender/version.rb
|
|
247
264
|
- spec/blender/blender_rspec.rb
|
|
265
|
+
- spec/blender/config_spec.rb
|
|
248
266
|
- spec/blender/discovery_spec.rb
|
|
249
267
|
- spec/blender/drivers/ssh_multi_spec.rb
|
|
250
268
|
- spec/blender/drivers/ssh_spec.rb
|
|
@@ -286,6 +304,7 @@ specification_version: 4
|
|
|
286
304
|
summary: A modular orchestration engine
|
|
287
305
|
test_files:
|
|
288
306
|
- spec/blender/blender_rspec.rb
|
|
307
|
+
- spec/blender/config_spec.rb
|
|
289
308
|
- spec/blender/discovery_spec.rb
|
|
290
309
|
- spec/blender/drivers/ssh_multi_spec.rb
|
|
291
310
|
- spec/blender/drivers/ssh_spec.rb
|