gorgon 0.7.1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- ODFiMzc4NjIzZTU3NmVjYjUyZDA0NDk0MWEzYTQ4MTg5MzZmZjBlZg==
5
- data.tar.gz: !binary |-
6
- YzljZGEyNTE5ZTU3OWFiYjhiNTdhMDU2YTkyMzg1NjU4YjA4MzI1Zg==
2
+ SHA1:
3
+ metadata.gz: 2697c5404498b1f1605746aa77f4812b3c106538
4
+ data.tar.gz: 8785ea9e49a5994a35e83f08b65106cfbc9369a4
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- ZWY1MWNmOWIwZjM3NjE4YTc5MTFiNjQzOWY0NmU2Y2FmMTUxOGJlZmMyZDky
10
- YmQ3NGRjNzM1MTMwNTY2Zjg5YzhmZWYxNDJiMTEzZWYyNmQ1ZGMxYzI5ZTAx
11
- NzkzMDA4N2U3MDc3NzJhYmMzNDk4NzE1YWEwNDNlNTZlZTBjMzM=
12
- data.tar.gz: !binary |-
13
- NjZiMTAyYTIxZjRjYzcxNmRhOTM0NmYwNmY2ZjU3MmU5MzdjMGE4Mjc4ZWUw
14
- MzQ1M2ZmZGQxYjM1ZjI1NzAyMGMyNmI1NjFhNDBhZDE1OGEyNmFlNDllZWE4
15
- NTJjOTY0ODhiYWYyZWZhYjZkODljYjZhMzczZWM5MWEwNjVhODY=
6
+ metadata.gz: b407fe9b9ccff0e2f6eefe038dff6bae1b71ab323622621fa98dbff3b92bb5ac55188990fdd7182b01349e7f6c13136f2c6b3ec1bdd1a76ddf214d0bce362361
7
+ data.tar.gz: e2846f05b7d0cd1213261356973a756dc75ee507a9c24e80e67bcbf3e22687ab59c15e8dd0449a07dcc9b27c3a9e3a8d319d7874cb54a33650adf94afe9c6144
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gorgon (0.7.1)
4
+ gorgon (0.8.0)
5
5
  amqp (~> 1.1.0)
6
6
  awesome_print
7
7
  colorize (~> 0.5.8)
data/README.md CHANGED
@@ -11,34 +11,20 @@ Gorgon provides a method for distributing the workload of running ruby test suit
11
11
 
12
12
  Installing Gorgon
13
13
  -----------------
14
- 1. `sudo apt-get install rabbitmq-server`
15
- 1. cd to your project
16
- 1. `gem install gorgon`
17
- 1. if using rails, `gorgon init rails` will create initial files for a typical rails project. Otherwise, you can use `gorgon init`
18
- 1. check gorgon.json to see and modify any necessary setting
19
- 1. add the following lines to your _database.yml_ file
20
-
21
- ```yaml
22
- remote_test: &remote_test
23
- <<: *defaults
24
- database: <my-app>_remote_test_<%=ENV['TEST_ENV_NUMBER']%>
25
- min_messages: warning
26
- ```
27
-
28
- Where `<<: *defaults` are the default values used in _database.yml_, like for example, adapter, username, password, and host. Replace `<my-app>` with a name to identify this application's dbs
14
+ This [tutorial](/tutorial.md) explains how to install gorgon in a sample app.
29
15
 
30
16
  Installing listener as a Daemon process (Ubuntu 9.10 or later)
31
17
  ----------------------------------------------------------------
32
18
  1. run `gorgon install_listener` from the directory where gorgon.json is
33
19
  1. run `gorgon ping` to check if the listener is running
34
20
 
35
- Try it out!
36
- -----------
37
- 1. run `gorgon` to run all the tests.
21
+ Gotchas
22
+ ----------------------------------------------------------------
38
23
 
39
- **NOTE:** if you get `cannot load such file -- qrack/qrack (LoadError)`, just add `gem 'gorgon', '~> 0.4.1' , :group => :remote_test` to your Gemfile, and run tests using `bundle exec gorgon`
24
+ * if you get `cannot load such file -- qrack/qrack (LoadError)`, just add `gem 'gorgon', '~> 0.4.1' , :group => :remote_test` to your Gemfile, and run tests using `bundle exec gorgon`
25
+ * If `gorgon install_listener` didn't work for you, you can try [these steps](/daemon_with_upstart_and_rvm.md)
40
26
 
41
- Also note that these steps are **not** meant to work on every project, they will only give you initial settings. You will probably have to modify the following files:
27
+ Also note that the steps in the tutorial are **not** meant to work on every project, they will only give you initial settings. You will probably have to modify the following files:
42
28
  * gorgon.json
43
29
  * test/gorgon_callbacks/after\_sync.rb
44
30
  * test/gorgon_callbacks/before\_creating\_workers.rb
@@ -49,13 +35,6 @@ Also note that these steps are **not** meant to work on every project, they will
49
35
 
50
36
  If you modify ~/.gorgon/gorgon_listener.json, make sure you restart the listener by running `sudo restart gorgon`
51
37
 
52
- Usage
53
- ---------------------
54
-
55
- To queue the current test suite, run `gorgon start`, or `gorgon`. _gorgon_ will read the application configuration out of _gorgon.json_, connect to the AMQP server, and publish the job.
56
-
57
- If you want to run the listener manually (didn't install Daemon process), you must run _gorgon job listeners_. To start a gorgon listener, run `gorgon listen`. This command will read the listener configuration out of _gorgon\_listener.json_, then start the listener process in the background.
58
-
59
38
  Configuration
60
39
  ---------------------
61
40
 
@@ -80,9 +59,6 @@ This file contains the listener-specific settings, such as:
80
59
 
81
60
  See [gorgon_listener.json example](/gorgon_listener.json.sample) for more details.
82
61
 
83
- ### Manually setting up gorgon listener as a daemon process (Ubuntu 9.10 or later)
84
- If `gorgon install_listener` didn't work for you, you can try [these steps](/daemon_with_upstart_and_rvm.md)
85
-
86
62
  Contributing
87
63
  ---------------------
88
64
  Read overview [architecture](/architecture.md)
data/bin/gorgon CHANGED
@@ -1,4 +1,5 @@
1
1
  require "rubygems"
2
+ require 'gorgon'
2
3
  require 'gorgon/originator'
3
4
  require 'gorgon/listener'
4
5
  require 'gorgon/rsync_daemon'
Binary file
data/gorgon.json.sample CHANGED
@@ -8,18 +8,20 @@
8
8
  },
9
9
 
10
10
  "job": {
11
- "sync_exclude": [
11
+ "sync": {
12
+ "exclude": [
13
+ ".git",
14
+ ".rvmrc",
12
15
  "tmp",
13
16
  "log",
14
- "doc",
15
- ".git",
16
- ".rvmrc"
17
- ],
17
+ "doc"
18
+ ],
19
+
20
+ "rsync_transport": "ssh" // or "anonymous"
21
+ },
22
+
18
23
  "callbacks": {
19
- "before_start": "test/gorgon_callbacks/before_start_callback.rb",
20
- "after_complete": "test/gorgon_callbacks/after_complete.rb",
21
- "before_creating_workers": "test/gorgon_callbacks/before_creating_workers.rb",
22
- "after_sync": "test/gorgon_callbacks/after_sync.rb"
24
+ "callbacks_class_file": "test/gorgon_callbacks.rb"
23
25
  }
24
26
  },
25
27
 
data/lib/gorgon.rb CHANGED
@@ -1,6 +1,13 @@
1
1
  require "gorgon/version"
2
2
  require "gorgon/originator"
3
3
  require "gorgon/listener"
4
+ require "gorgon/default_callbacks"
4
5
 
5
6
  module Gorgon
7
+ class << self
8
+ attr_accessor :callbacks
9
+ end
6
10
  end
11
+
12
+ # defaults
13
+ Gorgon.callbacks = Gorgon::DefaultCallbacks.new
@@ -1,25 +1,35 @@
1
1
  class CallbackHandler
2
2
  def initialize(config)
3
3
  @config = config || {}
4
+ load(@config[:callbacks_class_file]) if @config[:callbacks_class_file]
4
5
  end
5
6
 
6
- def before_start
7
- load(@config[:before_start]) if @config[:before_start]
7
+ def before_originate
8
+ cluster_id = Gorgon.callbacks.before_originate
9
+ return cluster_id if cluster_id.is_a?(String)
8
10
  end
9
11
 
10
- def after_complete
11
- load(@config[:after_complete]) if @config[:after_complete]
12
+ def after_sync
13
+ Gorgon.callbacks.after_sync
12
14
  end
13
15
 
14
16
  def before_creating_workers
15
- load(@config[:before_creating_workers]) if @config[:before_creating_workers]
17
+ Gorgon.callbacks.before_creating_workers
16
18
  end
17
19
 
18
- def after_sync
19
- load(@config[:after_sync]) if @config[:after_sync]
20
+ def before_start
21
+ Gorgon.callbacks.before_start
20
22
  end
21
23
 
22
24
  def after_creating_workers
23
- load(@config[:after_creating_workers]) if @config[:after_creating_workers]
25
+ Gorgon.callbacks.after_creating_workers
26
+ end
27
+
28
+ def after_complete
29
+ Gorgon.callbacks.after_complete
30
+ end
31
+
32
+ def after_job_finishes
33
+ Gorgon.callbacks.after_job_finishes
24
34
  end
25
35
  end
@@ -0,0 +1,25 @@
1
+ module Gorgon
2
+ class DefaultCallbacks
3
+ # @return cluster id. Cluster id is used to identify the queue in RabbitMQ
4
+ def before_originate
5
+ end
6
+
7
+ def after_sync
8
+ end
9
+
10
+ def before_creating_workers
11
+ end
12
+
13
+ def before_start
14
+ end
15
+
16
+ def after_creating_workers
17
+ end
18
+
19
+ def after_complete
20
+ end
21
+
22
+ def after_job_finishes
23
+ end
24
+ end
25
+ end
@@ -1,14 +1,13 @@
1
1
  require 'yajl'
2
2
 
3
3
  class JobDefinition
4
- attr_accessor :file_queue_name, :reply_exchange_name, :source_tree_path, :sync_exclude, :callbacks
4
+ attr_accessor :file_queue_name, :reply_exchange_name, :sync, :callbacks
5
5
 
6
6
  def initialize(opts={})
7
7
  @file_queue_name = opts[:file_queue_name]
8
8
  @reply_exchange_name = opts[:reply_exchange_name]
9
- @source_tree_path = opts[:source_tree_path]
10
9
  @callbacks = opts[:callbacks]
11
- @sync_exclude = opts[:sync_exclude]
10
+ @sync = opts[:sync]
12
11
  end
13
12
 
14
13
  def to_json
@@ -19,6 +18,12 @@ class JobDefinition
19
18
 
20
19
  #This can probably be done with introspection somehow, but this is way easier despite being very verbose
21
20
  def to_hash
22
- {:type => "job_definition", :file_queue_name => @file_queue_name, :reply_exchange_name => @reply_exchange_name, :source_tree_path => @source_tree_path, :sync_exclude => @sync_exclude, :callbacks => @callbacks}
21
+ {
22
+ :type => "job_definition",
23
+ :file_queue_name => @file_queue_name,
24
+ :reply_exchange_name => @reply_exchange_name,
25
+ :sync => @sync,
26
+ :callbacks => @callbacks
27
+ }
23
28
  end
24
29
  end
@@ -7,6 +7,7 @@ require "gorgon/version"
7
7
  require "gorgon/worker_manager"
8
8
  require "gorgon/crash_reporter"
9
9
  require "gorgon/gem_command_handler"
10
+ require 'gorgon/originator_protocol'
10
11
 
11
12
  require "yajl"
12
13
  require "gorgon_bunny/lib/gorgon_bunny"
@@ -44,7 +45,7 @@ class Listener
44
45
 
45
46
  def initialize_personal_job_queue
46
47
  @job_queue = @bunny.queue("", :exclusive => true)
47
- exchange = @bunny.exchange("gorgon.jobs", :type => :fanout)
48
+ exchange = @bunny.exchange(job_queue_name, :type => :fanout)
48
49
  @job_queue.bind(exchange)
49
50
  end
50
51
 
@@ -78,8 +79,7 @@ class Listener
78
79
  @job_definition = JobDefinition.new(payload)
79
80
  @reply_exchange = @bunny.exchange(@job_definition.reply_exchange_name, :auto_delete => true)
80
81
 
81
- @callback_handler = CallbackHandler.new(@job_definition.callbacks)
82
- copy_source_tree(@job_definition.source_tree_path, @job_definition.sync_exclude)
82
+ copy_source_tree(@job_definition.sync)
83
83
 
84
84
  if !@syncer.success? || !run_after_sync
85
85
  clean_up
@@ -100,7 +100,7 @@ class Listener
100
100
  def run_after_sync
101
101
  log "Running after_sync callback..."
102
102
  begin
103
- @callback_handler.after_sync
103
+ callback_handler.after_sync
104
104
  rescue Exception => e
105
105
  log_error "Exception raised when running after_sync callback_handler. Please, check your script in #{@job_definition.callbacks[:after_sync]}:"
106
106
  log_error e.message
@@ -117,10 +117,13 @@ class Listener
117
117
  true
118
118
  end
119
119
 
120
- def copy_source_tree source_tree_path, exclude
120
+ def callback_handler
121
+ @callback_handler ||= CallbackHandler.new(@job_definition.callbacks)
122
+ end
123
+
124
+ def copy_source_tree(sync_configuration)
121
125
  log "Downloading source tree to temp directory..."
122
- @syncer = SourceTreeSyncer.new source_tree_path
123
- @syncer.exclude = exclude
126
+ @syncer = SourceTreeSyncer.new sync_configuration
124
127
  @syncer.sync
125
128
  if @syncer.success?
126
129
  log "Command '#{@syncer.sys_command}' completed successfully."
@@ -145,7 +148,7 @@ class Listener
145
148
  stdin.write(@job_definition.to_json)
146
149
  stdin.close
147
150
 
148
- ignore, status = Process.waitpid2 pid
151
+ _, status = Process.waitpid2 pid
149
152
  log "Worker Manager #{pid} finished"
150
153
 
151
154
  if status.exitstatus != 0
@@ -172,6 +175,10 @@ class Listener
172
175
  reply_exchange.publish(Yajl::Encoder.encode(message))
173
176
  end
174
177
 
178
+ def job_queue_name
179
+ OriginatorProtocol.job_queue_name(configuration.fetch(:cluster_id, nil))
180
+ end
181
+
175
182
  def connection_information
176
183
  configuration[:connection]
177
184
  end
@@ -1,3 +1,4 @@
1
+ require 'gorgon'
1
2
  require 'gorgon/originator_protocol'
2
3
  require 'gorgon/configuration'
3
4
  require 'gorgon/job_state'
@@ -5,7 +6,8 @@ require 'gorgon/progress_bar_view'
5
6
  require 'gorgon/originator_logger'
6
7
  require 'gorgon/failures_printer'
7
8
  require 'gorgon/source_tree_syncer'
8
- require 'gorgon/shutdown_manager.rb'
9
+ require 'gorgon/shutdown_manager'
10
+ require 'gorgon/callback_handler'
9
11
 
10
12
  require 'awesome_print'
11
13
  require 'etc'
@@ -37,8 +39,7 @@ class Originator
37
39
  end
38
40
 
39
41
  def cancel_job
40
- ShutdownManager.new(protocol: @protocol,
41
- job_state: @job_state).cancel_job
42
+ ShutdownManager.new(protocol: @protocol, job_state: @job_state).cancel_job
42
43
  end
43
44
 
44
45
  def ctrl_c
@@ -54,31 +55,42 @@ class Originator
54
55
  exit 2
55
56
  end
56
57
 
58
+ cluster_id = callback_handler.before_originate
59
+
57
60
  push_source_code
58
61
 
59
- @protocol = OriginatorProtocol.new @logger
62
+ @protocol = OriginatorProtocol.new(@logger, cluster_id)
60
63
 
61
64
  EventMachine.run do
62
- @logger.log "Connecting..."
63
- @protocol.connect connection_information, :on_closed => method(:on_disconnect)
64
-
65
- @logger.log "Publishing files..."
66
- @protocol.publish_files files
67
- create_job_state_and_observers
68
-
69
- @logger.log "Publishing Job..."
70
- @protocol.publish_job job_definition
71
- @logger.log "Job Published"
65
+ publish_files_and_job
72
66
 
73
67
  @protocol.receive_payloads do |payload|
74
68
  handle_reply(payload)
75
69
  end
76
70
  end
71
+
72
+ callback_handler.after_job_finishes
73
+ end
74
+
75
+ def publish_files_and_job
76
+ @logger.log "Connecting..."
77
+ @protocol.connect connection_information, :on_closed => method(:on_disconnect)
78
+
79
+ @logger.log "Publishing files..."
80
+ @protocol.publish_files files
81
+ create_job_state_and_observers
82
+
83
+ @logger.log "Publishing Job..."
84
+ @protocol.publish_job job_definition
85
+ @logger.log "Job Published"
86
+ end
87
+
88
+ def callback_handler
89
+ @callback_handler ||= CallbackHandler.new(configuration[:job][:callbacks])
77
90
  end
78
91
 
79
92
  def push_source_code
80
- syncer = SourceTreeSyncer.new(source_tree_path)
81
- syncer.exclude = configuration[:job][:sync_exclude]
93
+ syncer = SourceTreeSyncer.new(sync_configuration)
82
94
  syncer.push
83
95
  if syncer.success?
84
96
  @logger.log "Command '#{syncer.sys_command}' completed successfully."
@@ -125,7 +137,7 @@ class Originator
125
137
  @job_state = JobState.new files.count
126
138
  @progress_bar_view = ProgressBarView.new @job_state
127
139
  @progress_bar_view.show
128
- failures_printer = FailuresPrinter.new @job_state
140
+ FailuresPrinter.new @job_state
129
141
  end
130
142
 
131
143
  def on_disconnect
@@ -143,20 +155,31 @@ class Originator
143
155
  end
144
156
 
145
157
  def job_definition
158
+ # TODO: remove duplication. Use sync_configuration
146
159
  job_config = configuration[:job]
147
- if !job_config.has_key?(:source_tree_path)
148
- job_config[:source_tree_path] = source_tree_path
149
- end
160
+ job_config[:sync] = {} unless job_config.has_key?(:sync)
161
+ job_config[:sync][:source_tree_path] = source_tree_path(job_config[:sync])
150
162
  JobDefinition.new(configuration[:job])
151
163
  end
152
164
 
153
165
  private
154
166
 
155
- def source_tree_path
167
+ def sync_configuration
168
+ configuration[:job].
169
+ fetch(:sync, {}).
170
+ merge(source_tree_path: source_tree_path(configuration[:job][:sync])
171
+ )
172
+ end
173
+
174
+ def source_tree_path(sync_config)
156
175
  hostname = Socket.gethostname
157
176
  source_code_root = File.basename(Dir.pwd)
158
177
 
159
- "rsync://#{file_server_host}:43434/src/#{hostname}_#{source_code_root}"
178
+ if sync_config && sync_config[:rsync_transport] == SourceTreeSyncer::RSYNC_TRANSPORT_SSH
179
+ "#{file_server_host}:#{hostname}_#{source_code_root}"
180
+ else
181
+ "rsync://#{file_server_host}:43434/src/#{hostname}_#{source_code_root}"
182
+ end
160
183
  end
161
184
 
162
185
  def file_server_host
@@ -5,10 +5,19 @@ require 'amqp'
5
5
  require 'uuidtools'
6
6
 
7
7
  class OriginatorProtocol
8
- def initialize logger
8
+ def initialize(logger, cluster_id=nil)
9
+ @job_queue_name = OriginatorProtocol.job_queue_name(cluster_id)
9
10
  @logger = logger
10
11
  end
11
12
 
13
+ def self.job_queue_name(cluster_id)
14
+ if cluster_id
15
+ "gorgon.jobs.#{cluster_id}"
16
+ else
17
+ 'gorgon.jobs'
18
+ end
19
+ end
20
+
12
21
  def connect connection_information, options={}
13
22
  @connection = AMQP.connect(connection_information)
14
23
  @channel = AMQP::Channel.new(@connection)
@@ -28,13 +37,13 @@ class OriginatorProtocol
28
37
  job_definition.file_queue_name = @file_queue.name
29
38
  job_definition.reply_exchange_name = @reply_exchange.name
30
39
 
31
- @channel.fanout("gorgon.jobs").publish(job_definition.to_json)
40
+ @channel.fanout(@job_queue_name).publish(job_definition.to_json)
32
41
  end
33
42
 
34
43
  def send_message_to_listeners type, body={}
35
44
  # TODO: we probably want to use a different exchange for this type of messages
36
45
  message = {:type => type, :reply_exchange_name => @reply_exchange.name, :body => body}
37
- @channel.fanout("gorgon.jobs").publish(Yajl::Encoder.encode(message))
46
+ @channel.fanout(@job_queue_name).publish(Yajl::Encoder.encode(message))
38
47
  end
39
48
 
40
49
  def receive_payloads
@@ -33,7 +33,10 @@ module Settings
33
33
  connection: {host: content.amqp_host},
34
34
  file_server: {host: content.file_server_host},
35
35
  job: {
36
- sync_exclude: content.sync_exclude
36
+ sync: {
37
+ exclude: content.sync_exclude
38
+ # TODO: add rsync_transport config
39
+ }
37
40
  },
38
41
  files: content.files
39
42
  }
@@ -1,25 +1,32 @@
1
1
  require 'open4'
2
2
 
3
3
  class SourceTreeSyncer
4
- attr_accessor :exclude
4
+ RSYNC_TRANSPORT_SSH = 'ssh'
5
+ RSYNC_TRANSPORT_ANONYMOUS = 'anonymous'
6
+
5
7
  attr_reader :sys_command, :output, :errors
6
8
 
7
9
  SYS_COMMAND = 'rsync'
8
- OPTS = "-azr --timeout=5 --delete"
9
- EXCLUDE_OPT = "--exclude"
10
-
11
- def initialize source_tree_path
12
- @source_tree_path = source_tree_path
13
- @exclude = []
10
+ OPTS = '-azr --timeout=5 --delete'
11
+ RSH_OPTS = 'ssh -o NumberOfPasswordPrompts=0 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i gorgon.pem'
12
+ EXCLUDE_OPT = '--exclude'
13
+
14
+ def initialize(sync_config)
15
+ if sync_config
16
+ @source_tree_path = sync_config[:source_tree_path]
17
+ @exclude = sync_config[:exclude]
18
+ @rsync_transport = sync_config[:rsync_transport]
19
+ end
14
20
  end
15
21
 
22
+ # TODO: rename sync to pull
16
23
  def sync
17
24
  return if blank_source_tree_path?
18
25
 
19
26
  @tempdir = Dir.mktmpdir("gorgon")
20
27
  Dir.chdir(@tempdir)
21
28
 
22
- @sys_command = "#{SYS_COMMAND} #{OPTS} #{build_exclude_opt} #{@source_tree_path}/ ."
29
+ @sys_command = "#{SYS_COMMAND} #{rsync_options} #{@source_tree_path}/ ."
23
30
 
24
31
  execute_command
25
32
  end
@@ -27,7 +34,7 @@ class SourceTreeSyncer
27
34
  def push
28
35
  return if blank_source_tree_path?
29
36
 
30
- @sys_command = "#{SYS_COMMAND} #{OPTS} #{build_exclude_opt} . #{@source_tree_path}"
37
+ @sys_command = "#{SYS_COMMAND} #{rsync_options} . #{@source_tree_path}"
31
38
 
32
39
  execute_command
33
40
  end
@@ -68,7 +75,15 @@ class SourceTreeSyncer
68
75
  end
69
76
  end
70
77
 
71
- def build_exclude_opt
78
+ def rsync_options
79
+ if @rsync_transport == RSYNC_TRANSPORT_SSH
80
+ "#{OPTS} #{exclude_options} --rsh='#{RSH_OPTS}'"
81
+ else
82
+ "#{OPTS} #{exclude_options}"
83
+ end
84
+ end
85
+
86
+ def exclude_options
72
87
  return "" if @exclude.nil? or @exclude.empty?
73
88
 
74
89
  exclude = [""] + @exclude
@@ -1,3 +1,3 @@
1
1
  module Gorgon
2
- VERSION = "0.7.1"
2
+ VERSION = "0.8.0"
3
3
  end
@@ -1,94 +1,39 @@
1
- require 'gorgon/callback_handler'
1
+ require 'gorgon'
2
2
 
3
3
  describe CallbackHandler do
4
4
 
5
5
  let(:config) {
6
6
  {
7
- :before_start => "some/file.rb",
8
- :after_complete => "some/other/file.rb",
9
- :before_creating_workers => "callbacks/before_creating_workers_file.rb",
10
- :after_sync => "callbacks/after_sync_file.rb",
11
- :after_creating_workers => "callbacks/after_creating_workers.rb"
7
+ :callbacks_class_file => "callback_file.rb"
12
8
  }
13
9
  }
14
-
15
- it "calls before hook" do
16
- handler = CallbackHandler.new(config)
17
-
18
- handler.should_receive(:load).with("some/file.rb")
19
-
20
- handler.before_start
21
- end
22
-
23
- it "does not attempt to load the before start script when before_start is not defined" do
24
- handler = CallbackHandler.new({})
25
-
26
- handler.should_not_receive(:load)
27
-
28
- handler.before_start
29
- end
30
-
31
- it "calls after hook" do
32
- handler = CallbackHandler.new(config)
33
-
34
- handler.should_receive(:load).with("some/other/file.rb")
35
-
36
- handler.after_complete
37
- end
38
-
39
- it "does not attempt to load the after complete script when before_start is not defined" do
40
- handler = CallbackHandler.new({})
41
-
42
- handler.should_not_receive(:load)
43
-
44
- handler.after_complete
10
+ before do
11
+ CallbackHandler.any_instance.stub(:load)
45
12
  end
46
13
 
47
- it "calls before fork hook" do
48
- handler = CallbackHandler.new(config)
14
+ it "loads callback file" do
15
+ CallbackHandler.any_instance.should_receive(:load).with config[:callbacks_class_file]
49
16
 
50
- handler.should_receive(:load).with("callbacks/before_creating_workers_file.rb")
51
-
52
- handler.before_creating_workers
53
- end
54
-
55
- it "does not attempt to load the before creating workers script when before_creating_workers is not defined" do
56
- handler = CallbackHandler.new({})
57
-
58
- handler.should_not_receive(:load)
59
-
60
- handler.before_creating_workers
17
+ CallbackHandler.new(config)
61
18
  end
62
19
 
63
- it "calls after sync hook" do
64
- handler = CallbackHandler.new(config)
65
-
66
- handler.should_receive(:load).with("callbacks/after_sync_file.rb")
20
+ it "does not load callback file if it's not specified" do
21
+ CallbackHandler.any_instance.should_not_receive(:load)
67
22
 
68
- handler.after_sync
23
+ CallbackHandler.new({})
69
24
  end
70
25
 
71
- it "does not attempt to load the after-sync script when after_sync is not defined" do
72
- handler = CallbackHandler.new({})
73
-
74
- handler.should_not_receive(:load)
75
-
76
- handler.after_sync
77
- end
78
-
79
- it "calls the after creating workers hook" do
80
- handler = CallbackHandler.new(config)
81
-
82
- handler.should_receive(:load).with("callbacks/after_creating_workers.rb")
83
-
84
- handler.after_creating_workers
85
- end
86
-
87
- it "does not attempt to load the after creating workers hook when after_creating_workers is not defined" do
88
- handler = CallbackHandler.new({})
89
-
90
- handler.should_not_receive(:load)
26
+ context "#before_originate" do
27
+ it "returns value from callbacks#before_originate if it's a string" do
28
+ handler = CallbackHandler.new(config)
29
+ Gorgon.callbacks.stub(:before_originate).and_return('my_job_id')
30
+ expect(handler.before_originate).to eq('my_job_id')
31
+ end
91
32
 
92
- handler.after_creating_workers
33
+ it "returns nil if callbacks#before_originate did not return a string" do
34
+ handler = CallbackHandler.new(config)
35
+ Gorgon.callbacks.stub(:before_originate).and_return(Object.new)
36
+ expect(handler.before_originate).to eq(nil)
37
+ end
93
38
  end
94
39
  end
@@ -8,7 +8,13 @@ describe JobDefinition do
8
8
 
9
9
  describe "#to_json" do
10
10
  it "should serialize itself to json" do
11
- expected_hash = {:type => "job_definition", :file_queue_name => "string 1", :reply_exchange_name => "string 2", :source_tree_path => "string 3", :sync_exclude => "string 4", :callbacks => {}}
11
+ expected_hash = {
12
+ :type => "job_definition",
13
+ :file_queue_name => "string 1",
14
+ :reply_exchange_name => "string 2",
15
+ :sync => {:exclude => "string 4"},
16
+ :callbacks => {}
17
+ }
12
18
 
13
19
  jd = JobDefinition.new(expected_hash)
14
20
 
@@ -88,7 +88,13 @@ describe Listener do
88
88
  listener.initialize_personal_job_queue
89
89
  end
90
90
 
91
- it "binds the exchange to the queue" do
91
+ it "build job_queue_name using job_id from configuration" do
92
+ Listener.any_instance.stub(:configuration).and_return(:cluster_id => 'cluster5')
93
+ bunny.should_receive(:exchange).with('gorgon.jobs.cluster5', anything).and_return(exchange)
94
+ listener.initialize_personal_job_queue
95
+ end
96
+
97
+ it "binds the exchange to the queue. Uses gorgon.jobs if there is no job_queue_name in configuration" do
92
98
  bunny.should_receive(:exchange).with("gorgon.jobs", :type => :fanout).and_return(exchange)
93
99
  queue.should_receive(:bind).with(exchange)
94
100
  listener.initialize_personal_job_queue
@@ -177,8 +183,7 @@ describe Listener do
177
183
 
178
184
  describe "#run_job" do
179
185
  let(:payload) {{
180
- :source_tree_path => "path/to/source",
181
- :sync_exclude => ["log"], :callbacks => {:a_callback => "path/to/callback"}
186
+ :sync => {:source_tree_path => "path/to/source", :exclude => ["log"]}, :callbacks => {:a_callback => "path/to/callback"}
182
187
  }}
183
188
 
184
189
  let(:syncer) { double("SourceTreeSyncer", :sync => nil, :exclude= => nil, :success? => true,
@@ -196,8 +201,9 @@ describe Listener do
196
201
  end
197
202
 
198
203
  it "copy source tree" do
199
- SourceTreeSyncer.should_receive(:new).once.with("path/to/source").and_return syncer
200
- syncer.should_receive(:exclude=).with(["log"])
204
+ SourceTreeSyncer.should_receive(:new).once.
205
+ with(source_tree_path: "path/to/source", exclude: ["log"]).
206
+ and_return(syncer)
201
207
  syncer.should_receive(:sync)
202
208
  syncer.should_receive(:success?).and_return(true)
203
209
  @listener.run_job(payload)
@@ -67,8 +67,7 @@ describe OriginatorProtocol do
67
67
 
68
68
  describe "#publish_job" do
69
69
  before do
70
- @originator_p.connect @conn_information
71
- @originator_p.publish_files []
70
+ connect_and_publish_files(@originator_p)
72
71
  end
73
72
 
74
73
  it "add queue's names to job_definition and fanout using 'gorgon.jobs' exchange" do
@@ -80,6 +79,14 @@ describe OriginatorProtocol do
80
79
  exchange.should_receive(:publish).with(exp_job_definition.to_json)
81
80
  @originator_p.publish_job JobDefinition.new
82
81
  end
82
+
83
+ it "uses cluster_id in job_queue_name, when it is specified" do
84
+ originator_p = connect_and_publish_files(OriginatorProtocol.new(logger, "cluster1"))
85
+
86
+ channel.should_receive(:fanout).with("gorgon.jobs.cluster1")
87
+
88
+ originator_p.publish_job JobDefinition.new
89
+ end
83
90
  end
84
91
 
85
92
  describe "#send_message_to_listeners" do
@@ -150,4 +157,10 @@ describe OriginatorProtocol do
150
157
  @originator_p.disconnect
151
158
  end
152
159
  end
160
+
161
+ def connect_and_publish_files(originator_p)
162
+ originator_p.connect @conn_information
163
+ originator_p.publish_files []
164
+ originator_p
165
+ end
153
166
  end
@@ -20,7 +20,7 @@ describe Originator do
20
20
  @originator = Originator.new
21
21
  end
22
22
 
23
- describe "#publish_job" do
23
+ describe "#publish" do
24
24
  before do
25
25
  stub_methods
26
26
  end
@@ -55,6 +55,24 @@ describe Originator do
55
55
 
56
56
  expect { @originator.publish }.to raise_error(SystemExit)
57
57
  end
58
+
59
+ it "calls before_originate callback" do
60
+ CallbackHandler.any_instance.should_receive(:before_originate)
61
+ @originator.publish
62
+ end
63
+
64
+ it "uses results of before_originate callback to build a job_queue_name" do
65
+ CallbackHandler.any_instance.stub(:before_originate).and_return('job_1')
66
+ OriginatorProtocol.should_receive(:new).with(anything, 'job_1')
67
+
68
+ @originator.publish
69
+ end
70
+
71
+ it "calls after_job_finishes callback" do
72
+ CallbackHandler.any_instance.should_receive(:after_job_finishes)
73
+
74
+ @originator.publish
75
+ end
58
76
  end
59
77
 
60
78
  describe "#cancel_job" do
@@ -136,17 +154,23 @@ describe Originator do
136
154
  @originator.job_definition.should equal job_definition
137
155
  end
138
156
 
139
- it "builds source_tree_path if it was not specified in the configuration" do
157
+ it "builds anonymous source_tree_path if it was not specified in the configuration" do
140
158
  @originator.stub(:configuration).and_return(configuration.merge(:file_server => {:host => 'host-name'}))
141
159
  Socket.stub(:gethostname => 'my-host')
142
160
  Dir.stub(:pwd => 'dir')
143
161
 
144
- @originator.job_definition.source_tree_path.should == "rsync://host-name:43434/src/my-host_dir"
162
+ @originator.job_definition.sync[:source_tree_path].should == "rsync://host-name:43434/src/my-host_dir"
145
163
  end
146
164
 
147
- it "returns source_tree_path specified in configuration if it is present" do
148
- @originator.stub(:configuration).and_return({:job => {:source_tree_path => "login@host:path/to/dir"}})
149
- @originator.job_definition.source_tree_path.should == "login@host:path/to/dir"
165
+ it "builds ssh source_tree_path if using ssh rsync transport" do
166
+ @originator.stub(:configuration).and_return(configuration.merge(
167
+ :file_server => {:host => 'host-name'},
168
+ :job => { :sync => { :rsync_transport => 'ssh'}}
169
+ ))
170
+ Socket.stub(:gethostname => 'my-host')
171
+ Dir.stub(:pwd => 'dir')
172
+
173
+ @originator.job_definition.sync[:source_tree_path].should == "host-name:my-host_dir"
150
174
  end
151
175
  end
152
176
 
@@ -1,7 +1,6 @@
1
1
  require 'gorgon/source_tree_syncer'
2
2
 
3
- describe SourceTreeSyncer.new("") do
4
- it { should respond_to :exclude= }
3
+ describe SourceTreeSyncer.new(source_tree_path: "") do
5
4
  it { should respond_to :sync }
6
5
  it { should respond_to :push }
7
6
  it { should respond_to :sys_command }
@@ -16,7 +15,7 @@ describe SourceTreeSyncer.new("") do
16
15
  let(:status) { double("Process Status", :exitstatus => 0)}
17
16
 
18
17
  before do
19
- @syncer = SourceTreeSyncer.new "path/to/source"
18
+ @syncer = SourceTreeSyncer.new(source_tree_path: "path/to/source")
20
19
  stub_utilities_methods
21
20
  end
22
21
 
@@ -25,11 +24,12 @@ describe SourceTreeSyncer.new("") do
25
24
  Dir.should_receive(:mktmpdir).and_return("tmp/dir")
26
25
  Dir.should_receive(:chdir).with("tmp/dir")
27
26
  @syncer.sync
27
+ expect(@syncer.success?).to be_true, "Syncer error: #{@syncer.errors}"
28
28
  end
29
29
 
30
30
  context "invalid source_tree_path" do
31
31
  it "gives error if source_tree_path is empty string" do
32
- syncer = SourceTreeSyncer.new " "
32
+ syncer = SourceTreeSyncer.new(source_tree_path: " ")
33
33
  Dir.should_not_receive(:mktmpdir)
34
34
  syncer.sync
35
35
  syncer.success?.should be_false
@@ -53,7 +53,7 @@ describe SourceTreeSyncer.new("") do
53
53
  end
54
54
 
55
55
  it "exclude files when they are specified" do
56
- @syncer.exclude = ["log", ".git"]
56
+ @syncer = SourceTreeSyncer.new(source_tree_path: "path/to/source", exclude: ["log", ".git"])
57
57
  Open4.should_receive(:popen4).with(/--exclude log --exclude .git/)
58
58
  @syncer.sync
59
59
  end
@@ -98,7 +98,7 @@ describe SourceTreeSyncer.new("") do
98
98
 
99
99
  describe "#remove_temp_dir" do
100
100
  before do
101
- @syncer = SourceTreeSyncer.new "path/to/source"
101
+ @syncer = SourceTreeSyncer.new(source_tree_path: "path/to/source")
102
102
  stub_utilities_methods
103
103
  @syncer.sync
104
104
  end
@@ -20,7 +20,7 @@ describe WorkerManager do
20
20
  describe ".build" do
21
21
  it "should load_configuration_from_file" do
22
22
  STDIN.should_receive(:read).and_return '{"source_tree_path":"path/to/source",
23
- "sync_exclude":["log"]}'
23
+ "sync":{"exclude":["log"]}}'
24
24
 
25
25
  Configuration.should_receive(:load_configuration_from_file).with("file.json").and_return({})
26
26
 
data/tutorial.md ADDED
@@ -0,0 +1,97 @@
1
+ # Gorgon Setup Tutorial
2
+
3
+ This is an example on how to setup [Gorgon](https://github.com/Fitzsimmons/Gorgon). For this tutorial, we will use a version of a Rails app from [Ruby on Rails Tutorial](https://www.railstutorial.org/) by Michael Hartl.
4
+
5
+ ### Setup the sample Rails app
6
+
7
+ First, let's clone and setup the sample Rails app.
8
+
9
+ ```bash
10
+ git clone git@github.com:arturopie/sample_app-1.git
11
+ cd sample_app-1/
12
+ bundle
13
+ rake db:setup
14
+ rake db:test:prepare
15
+ ```
16
+
17
+ Run `rspec` and make sure all tests pass.
18
+
19
+
20
+ ### Setup Gorgon
21
+
22
+ 1. Install [RabbitMQ](https://www.rabbitmq.com/download.html).
23
+
24
+ 2. Add Gorgon to the Gemfile: `gem 'gorgon', '0.7.1'`
25
+
26
+ 3. `bundle`
27
+
28
+ 4. Run `gorgon init rails` to create initial files for a typical Rails project. Gorgon will ask you to enter the AMQP host name and the File Server host name. Just press enter to use the default host (localhost).
29
+
30
+ Gorgon does not use Rails environment 'test' to run tests. Instead, one of the callbacks generated by `gorgon init rails` will set Rails environment to 'remote_test' before running any test. We need to setup this new environment in our Rails app.
31
+
32
+ 5. Add the following lines to config/database.yml. The callbacks will set TEST_ENV_NUMBER to its process id so that no two Gorgon runners use the same database.
33
+
34
+ ```yml
35
+ remote_test: &remote_test
36
+ adapter: sqlite3
37
+ database: db/remote_test_<%=ENV['TEST_ENV_NUMBER']%>
38
+ pool: 5
39
+ timeout: 5000
40
+ ```
41
+
42
+ 6. In the Gemfile, add :remote_test to every :test group. For example:
43
+
44
+ ```ruby
45
+ ...
46
+ group :development, :test, :remote_test do
47
+ gem 'sqlite3', '1.3.5'
48
+ ...
49
+ group :test, :remote_test do
50
+ gem 'capybara', '1.1.2'
51
+ ...
52
+ ```
53
+
54
+
55
+ 7. Create config/environments/remote_test.rb file. This file can be just a copy of config/environments/test.rb
56
+
57
+ ```bash
58
+ cp config/environments/test.rb config/environments/remote_test.rb
59
+ ```
60
+
61
+ ### Run Gorgon Listener
62
+
63
+ 1. Download a listener configuration sample:
64
+
65
+ ```bash
66
+ wget https://raw.githubusercontent.com/Fitzsimmons/Gorgon/master/gorgon_listener.json.sample
67
+ mv gorgon_listener.json.sample gorgon_listener.json
68
+ ```
69
+
70
+ 2. Start listener
71
+
72
+ ```bash
73
+ gorgon listen
74
+ ```
75
+
76
+ ### Run File Server
77
+
78
+ ```bash
79
+ mkdir -p ~/.gorgon/file_dir # here is where gorgon will push files under test
80
+ gorgon start_rsync ~/.gorgon/file_dir
81
+ ```
82
+
83
+ ### Run Gorgon
84
+
85
+ Now, you are ready to run Gorgon.
86
+
87
+ ```bash
88
+ gorgon
89
+ ```
90
+
91
+ ### Result
92
+
93
+ If everything went well, this is what you should see:
94
+
95
+ ![image](/gorgon-done-screenshot.png)
96
+
97
+  
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gorgon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Fitzsimmons
@@ -12,34 +12,34 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2014-10-11 00:00:00.000000000 Z
15
+ date: 2014-11-14 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: rake
19
19
  requirement: !ruby/object:Gem::Requirement
20
20
  requirements:
21
- - - ! '>='
21
+ - - '>='
22
22
  - !ruby/object:Gem::Version
23
23
  version: '0'
24
24
  type: :development
25
25
  prerelease: false
26
26
  version_requirements: !ruby/object:Gem::Requirement
27
27
  requirements:
28
- - - ! '>='
28
+ - - '>='
29
29
  - !ruby/object:Gem::Version
30
30
  version: '0'
31
31
  - !ruby/object:Gem::Dependency
32
32
  name: test-unit
33
33
  requirement: !ruby/object:Gem::Requirement
34
34
  requirements:
35
- - - ! '>='
35
+ - - '>='
36
36
  - !ruby/object:Gem::Version
37
37
  version: '0'
38
38
  type: :development
39
39
  prerelease: false
40
40
  version_requirements: !ruby/object:Gem::Requirement
41
41
  requirements:
42
- - - ! '>='
42
+ - - '>='
43
43
  - !ruby/object:Gem::Version
44
44
  version: '0'
45
45
  - !ruby/object:Gem::Dependency
@@ -74,14 +74,14 @@ dependencies:
74
74
  name: awesome_print
75
75
  requirement: !ruby/object:Gem::Requirement
76
76
  requirements:
77
- - - ! '>='
77
+ - - '>='
78
78
  - !ruby/object:Gem::Version
79
79
  version: '0'
80
80
  type: :runtime
81
81
  prerelease: false
82
82
  version_requirements: !ruby/object:Gem::Requirement
83
83
  requirements:
84
- - - ! '>='
84
+ - - '>='
85
85
  - !ruby/object:Gem::Version
86
86
  version: '0'
87
87
  - !ruby/object:Gem::Dependency
@@ -173,6 +173,7 @@ files:
173
173
  - architecture.md
174
174
  - bin/gorgon
175
175
  - daemon_with_upstart_and_rvm.md
176
+ - gorgon-done-screenshot.png
176
177
  - gorgon.gemspec
177
178
  - gorgon.json.sample
178
179
  - gorgon_listener.json.sample
@@ -182,6 +183,7 @@ files:
182
183
  - lib/gorgon/colors.rb
183
184
  - lib/gorgon/configuration.rb
184
185
  - lib/gorgon/crash_reporter.rb
186
+ - lib/gorgon/default_callbacks.rb
185
187
  - lib/gorgon/failures_printer.rb
186
188
  - lib/gorgon/g_logger.rb
187
189
  - lib/gorgon/gem_command_handler.rb
@@ -346,6 +348,7 @@ files:
346
348
  - spec/unknown_runner_spec.rb
347
349
  - spec/worker_manager_spec.rb
348
350
  - spec/worker_spec.rb
351
+ - tutorial.md
349
352
  homepage: ''
350
353
  licenses: []
351
354
  metadata: {}
@@ -355,17 +358,17 @@ require_paths:
355
358
  - lib
356
359
  required_ruby_version: !ruby/object:Gem::Requirement
357
360
  requirements:
358
- - - ! '>='
361
+ - - '>='
359
362
  - !ruby/object:Gem::Version
360
363
  version: '0'
361
364
  required_rubygems_version: !ruby/object:Gem::Requirement
362
365
  requirements:
363
- - - ! '>='
366
+ - - '>='
364
367
  - !ruby/object:Gem::Version
365
368
  version: '0'
366
369
  requirements: []
367
370
  rubyforge_project: gorgon
368
- rubygems_version: 2.2.2
371
+ rubygems_version: 2.0.14
369
372
  signing_key:
370
373
  specification_version: 4
371
374
  summary: Distributed testing for ruby with centralized management