gorgon 0.7.1 → 0.8.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 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