mamiya 0.0.1.alpha19 → 0.0.1.alpha20

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,60 @@
1
+ require 'mamiya/agent/tasks/notifyable'
2
+ require 'mamiya/steps/fetch'
3
+ require 'mamiya/storages/abstract'
4
+
5
+ module Mamiya
6
+ class Agent
7
+ module Tasks
8
+ class Fetch < Notifyable
9
+ def run
10
+ logger.info "Fetching #{application}/#{package}"
11
+
12
+ take_interval
13
+ step.run!
14
+ order_cleaning
15
+ rescue Mamiya::Storages::Abstract::AlreadyFetched
16
+ logger.info "It has already fetched; skipping."
17
+ end
18
+
19
+ private
20
+
21
+ def take_interval
22
+ fetch_sleep = config[:fetch_sleep]
23
+ wait = rand(fetch_sleep)
24
+
25
+ @logger.info "Sleeping #{wait} sec before starting fetch"
26
+ rand(wait)
27
+ end
28
+
29
+ def order_cleaning
30
+ task_queue.enqueue(:clean, {})
31
+ end
32
+
33
+ def application
34
+ task['app']
35
+ end
36
+
37
+ def package
38
+ task['pkg']
39
+ end
40
+
41
+ def destination
42
+ @destination ||= File.join(packages_dir, application)
43
+ end
44
+
45
+ def packages_dir
46
+ @packages_dir ||= config && config[:packages_dir]
47
+ end
48
+
49
+ def step
50
+ @step ||= Mamiya::Steps::Fetch.new(
51
+ application: application,
52
+ package: package,
53
+ destination: destination,
54
+ config: config,
55
+ )
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,30 @@
1
+ require 'mamiya/agent'
2
+ require 'mamiya/agent/tasks/abstract'
3
+
4
+ module Mamiya
5
+ class Agent
6
+ module Tasks
7
+ class Notifyable < Abstract
8
+ def execute
9
+ agent.trigger('task', action: 'start',
10
+ task: task
11
+ )
12
+
13
+ super
14
+
15
+ ensure
16
+ if error
17
+ agent.trigger('task', action: 'error',
18
+ error: error.class.name,
19
+ task: task,
20
+ )
21
+ else
22
+ agent.trigger('task', action: 'finish',
23
+ task: task,
24
+ )
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -10,7 +10,7 @@ require 'thor'
10
10
  module Mamiya
11
11
  class CLI < Thor
12
12
  class Client < Thor
13
- class_option :master, aliases: '-u', type: :string
13
+ class_option :master, aliases: '-u', type: :string, default: 'http://localhost:7761/'
14
14
  class_option :application, aliases: %w(-a --app), type: :string
15
15
 
16
16
  desc "list-applications", "list applications"
data/lib/mamiya/master.rb CHANGED
@@ -27,7 +27,7 @@ module Mamiya
27
27
  end
28
28
 
29
29
  def start
30
- # Override and stop starting fetcher
30
+ # Override and stop starting task_queue
31
31
  web_start
32
32
  serf_start
33
33
  monitor_start
@@ -38,11 +38,6 @@ module Mamiya
38
38
  super
39
39
  end
40
40
 
41
- # XXX: dupe? Mamiya::Agent::Actions#distribute
42
- def distribute(application, package)
43
- trigger(:fetch, coalesce: false, application: application, package: package)
44
- end
45
-
46
41
  def storage(app)
47
42
  config.storage_class.new(
48
43
  config[:storage].merge(
@@ -89,9 +84,9 @@ module Mamiya
89
84
  options = config[:web] || {}
90
85
  rack_options = {
91
86
  app: self.web,
92
- Port: options[:port].to_i,
93
- Host: options[:bind],
94
- environment: options[:environment],
87
+ Port: options[:port] ? options[:port].to_i : 7761,
88
+ Host: options[:bind] || '0.0.0.0', # TODO: IPv6
89
+ environment: options[:environment] || :development,
95
90
  server: options[:server],
96
91
  Logger: logger['web']
97
92
  }
@@ -2,55 +2,72 @@ require 'mamiya/master'
2
2
 
3
3
  module Mamiya
4
4
  class Master
5
+ # XXX: TODO:
5
6
  module AgentMonitorHandlers
6
- def fetch_result__ack(status, payload, event)
7
- status['fetcher'] ||= {}
8
- status['fetcher']['pending'] = payload['pending']
7
+ def task__start(status, payload, event)
8
+ task = payload['task']
9
9
 
10
- status['fetcher']['pending_jobs'] ||= []
11
- status['fetcher']['pending_jobs'] << [payload['application'], payload['package']]
10
+ status['queues'] ||= {}
11
+ status['queues'][task['task']] ||= {'queue' => [], 'working' => nil}
12
+
13
+ status['queues'][task['task']]['working'] = task
14
+ status['queues'][task['task']]['queue'].delete task
12
15
  end
13
16
 
14
- def fetch_result__start(status, payload, event)
15
- status['fetcher'] ||= {}
16
- status['fetcher']['fetching'] = [payload['application'], payload['package']]
17
+ def task__finalize(status, payload, event)
18
+ task = payload['task']
17
19
 
18
- logger.debug "#{status['name']} started to fetch #{payload['application']}/#{payload['package']}"
20
+ status['queues'] ||= {}
21
+ status['queues'][task['task']] ||= {'queue' => [], 'working' => nil}
19
22
 
20
- status['fetcher']['pending_jobs'] ||= []
21
- status['fetcher']['pending_jobs'].delete [payload['application'], payload['package']]
23
+ s = status['queues'][task['task']]
24
+ if s['working'] == task
25
+ s['working'] = nil
26
+ end
27
+ status['queues'][task['task']]['queue'].delete task
22
28
  end
23
29
 
24
- def fetch_result__error(status, payload, event)
25
- status['fetcher'] ||= {}
30
+ def task__finish(status, payload, event)
31
+ task = payload['task']
32
+ logger.error "#{status['name']} finished task #{task['task']}: #{payload['error']}"
26
33
 
27
- logger.error "#{status['name']} failed to fetch #{payload['application']}/#{payload['package']}: #{payload['error']}"
34
+ task__finalize(status, payload, event)
28
35
 
29
- if status['fetcher']['fetching'] == [payload['application'], payload['package']]
30
- status['fetcher']['fetching'] = nil
36
+ method_name = "task___#{task['task']}__finish"
37
+ if self.respond_to?(method_name)
38
+ __send__ method_name, status, task
31
39
  end
32
40
  end
33
41
 
34
- def fetch_result__success(status, payload, event)
35
- status['fetcher'] ||= {}
42
+ def task__error(status, payload, event)
43
+ task = payload['task']
44
+ logger.error "#{status['name']} failed task #{task['task']}: #{payload['error']}"
36
45
 
37
- logger.info "#{status['name']} fetched #{payload['application']}/#{payload['package']}"
46
+ task__finalize(status, payload, event)
38
47
 
39
- if status['fetcher']['fetching'] == [payload['application'], payload['package']]
40
- status['fetcher']['fetching'] = nil
48
+ method_name = "task___#{task['task']}__error"
49
+ if self.respond_to?(method_name)
50
+ __send__ method_name, status, task, error
41
51
  end
52
+ end
53
+
42
54
 
55
+
56
+ # XXX: move task finish handlers into tasks/
57
+ def task___fetch__finish(status, task)
43
58
  status['packages'] ||= {}
44
- status['packages'][payload['application']] ||= []
45
- status['packages'][payload['application']] << payload['package']
59
+ status['packages'][task['app']] ||= []
60
+
61
+ unless status['packages'][task['app']].include?(task['pkg'])
62
+ status['packages'][task['app']] << task['pkg']
63
+ end
46
64
  end
47
65
 
48
- def fetch_result__remove(status, payload, event)
66
+ def pkg__remove(status, payload, event)
49
67
  status['packages'] ||= {}
50
68
  packages = status['packages'][payload['application']]
51
69
  packages.delete(payload['package']) if packages
52
70
  end
53
-
54
71
  end
55
72
  end
56
73
  end
@@ -21,7 +21,8 @@ module Mamiya
21
21
  end
22
22
 
23
23
  get '/' do
24
- "mamiya v#{Mamiya::VERSION}"
24
+ content_type 'text/plain'
25
+ "mamiya v#{Mamiya::VERSION}\n"
25
26
  end
26
27
 
27
28
  get '/packages/:application' do
@@ -88,15 +89,29 @@ module Mamiya
88
89
 
89
90
  statuses.each do |name, status|
90
91
  next if status["master"]
91
- if status["packages"] && status["packages"][params[:application]] &&
92
- status["packages"][params[:application]].include?(params[:package])
92
+ queue = status["queues"] && status["queues"]["fetch"]
93
+ packages = status["packages"] && status["packages"][params[:application]]
94
+
95
+ task_matcher = -> (task) do
96
+ task["task"] == "fetch" &&
97
+ task["app"] == params[:application] &&
98
+ task["pkg"] == params[:package]
99
+ end
100
+
101
+
102
+ case
103
+ when packages && packages.include?(params[:package])
93
104
 
94
105
  result[:distributed] << name
95
- elsif status["fetcher"] && status["fetcher"]["fetching"] && status["fetcher"]["fetching"] == pkg_array
106
+
107
+ when queue && queue["working"] && task_matcher.call(queue['working'])
108
+
96
109
  result[:fetching] << name
97
- elsif status["fetcher"]["pending_jobs"] &&
98
- status["fetcher"]["pending_jobs"].include?(pkg_array)
110
+
111
+ when queue['queue'].any?(&task_matcher)
112
+
99
113
  result[:queued] << name
114
+
100
115
  else
101
116
  result[:not_distributed] << name
102
117
  end
@@ -113,7 +128,7 @@ module Mamiya
113
128
  when 0 < result[:queued_count] || 0 < result[:fetching_count]
114
129
  status = :distributing
115
130
  when 0 < result[:distributed_count] && result[:distributed_count] < total
116
- status = :well_distributed
131
+ status = :partially_distributed
117
132
  when result[:distributed_count] == total
118
133
  status = :distributed
119
134
  else
@@ -1,3 +1,3 @@
1
1
  module Mamiya
2
- VERSION = "0.0.1.alpha19"
2
+ VERSION = "0.0.1.alpha20"
3
3
  end
data/mamiya.gemspec CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ["lib"]
20
20
 
21
21
  spec.add_runtime_dependency "thor", ">= 0.18.1"
22
- spec.add_runtime_dependency "aws-sdk-core", "2.0.0.rc14"
22
+ spec.add_runtime_dependency "aws-sdk-core", "2.0.0.rc15"
23
23
  spec.add_runtime_dependency "term-ansicolor", ">= 1.3.0"
24
24
  unless ENV["MAMIYA_VILLEIN_PATH"]
25
25
  spec.add_runtime_dependency "villein", ">= 0.3.2"
@@ -10,7 +10,6 @@ require_relative '../support/dummy_serf.rb'
10
10
 
11
11
  describe Mamiya::Agent::Actions do
12
12
  let(:serf) { DummySerf.new }
13
- let(:fetcher) { double('fetcher', start!: nil) }
14
13
 
15
14
  let(:config) do
16
15
  {serf: {agent: {rpc_addr: '127.0.0.1:17373', bind: '127.0.0.1:17946'}}}
@@ -18,7 +17,6 @@ describe Mamiya::Agent::Actions do
18
17
 
19
18
  before do
20
19
  allow(Villein::Agent).to receive(:new).and_return(serf)
21
- allow(Mamiya::Agent::Fetcher).to receive(:new).and_return(fetcher)
22
20
  end
23
21
 
24
22
  subject(:agent) { Mamiya::Agent.new(config) }
@@ -26,13 +24,9 @@ describe Mamiya::Agent::Actions do
26
24
 
27
25
  describe "#distribute" do
28
26
  it "sends fetch request" do
29
- expect(serf).to receive(:event).with(
30
- 'mamiya:fetch',
31
- {application: 'app', package: 'pkg'}.to_json,
32
- coalesce: false
33
- )
27
+ expect(agent).to receive(:trigger).with('task', task: 'fetch', app: 'myapp', pkg: 'mypkg', coalesce: false)
34
28
 
35
- agent.distribute('app', 'pkg')
29
+ agent.distribute('myapp', 'mypkg')
36
30
  end
37
31
  end
38
32
  end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ require 'json'
4
+ require 'villein/event'
5
+
6
+ require 'mamiya/agent/handlers/task'
7
+
8
+ describe Mamiya::Agent::Handlers::Task do
9
+ let(:event) do
10
+ Villein::Event.new(
11
+ {
12
+ 'SERF_EVENT' => 'user',
13
+ 'SERF_USER_EVENT' => 'mamiya:task',
14
+ },
15
+ payload: {
16
+ task: 'fetch',
17
+ application: 'app',
18
+ package: 'package',
19
+ }.to_json,
20
+ )
21
+ end
22
+
23
+ let(:task_queue) { double('task_queue', enqueue: nil) }
24
+
25
+ let(:agent) do
26
+ double('agent', task_queue: task_queue)
27
+ end
28
+
29
+ subject(:handler) { described_class.new(agent, event) }
30
+
31
+ before do
32
+ end
33
+
34
+ it "enqueue a request" do
35
+ expect(task_queue).to receive(:enqueue).with(:fetch, 'task' => 'fetch', 'application' => 'app', 'package' => 'package')
36
+
37
+ handler.run!
38
+ end
39
+ end
@@ -0,0 +1,246 @@
1
+ require 'spec_helper'
2
+ require 'thread'
3
+ require 'mamiya/agent/tasks/abstract'
4
+ require 'mamiya/agent/task_queue'
5
+
6
+ describe Mamiya::Agent::TaskQueue do
7
+ let(:agent) do
8
+ double('agent')
9
+ end
10
+
11
+ let(:task_class_root) do
12
+ Class.new(Mamiya::Agent::Tasks::Abstract) do
13
+ def self.runs
14
+ @runs ||= []
15
+ end
16
+
17
+ def self.locks
18
+ @locks ||= {}
19
+ end
20
+
21
+ def self.locks_lock
22
+ @locks_lock ||= Mutex.new
23
+ end
24
+
25
+ def execute
26
+ self.class.runs << task.dup
27
+ if task['wait']
28
+ begin
29
+ queue = Queue.new
30
+ self.class.locks_lock.synchronize do
31
+ self.class.locks[self.task] = queue
32
+ end
33
+ queue.pop
34
+ ensure
35
+ self.class.locks_lock.synchronize do
36
+ self.class.locks.delete self.task
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+
45
+ let(:task_class_a) do
46
+ Class.new(task_class_root) do
47
+ def self.identifier
48
+ 'a'
49
+ end
50
+ end
51
+ end
52
+
53
+ let(:task_class_b) do
54
+ Class.new(task_class_root) do
55
+ def self.identifier
56
+ 'b'
57
+ end
58
+ end
59
+ end
60
+
61
+ subject(:queue) do
62
+ described_class.new(agent, task_classes: [task_class_a, task_class_b])
63
+ end
64
+
65
+ describe "lifecycle (#start!, #stop!)" do
66
+ it "can start and stop" do
67
+ expect(queue).not_to be_running
68
+ expect(queue.worker_threads).to be_nil
69
+
70
+ queue.start!
71
+
72
+ expect(queue).to be_running
73
+
74
+ expect(queue.worker_threads).to be_a_kind_of(Hash)
75
+ expect(queue.worker_threads.values.all? { |v| v.kind_of?(Thread) }).to be_true
76
+ expect(queue.worker_threads.values.all? { |v| v.alive? }).to be_true
77
+ threads = queue.worker_threads.dup
78
+
79
+ queue.stop!
80
+
81
+ expect(queue).not_to be_running
82
+ expect(queue.worker_threads).to be_nil
83
+ expect(threads.each_value.all? { |v| !v.alive? }).to be_true
84
+ end
85
+
86
+ it "can stop gracefully"
87
+ end
88
+
89
+ describe "work loop (#enqueue, #running?, #working, #status)" do
90
+ after do
91
+ queue.stop! if queue.running?
92
+ end
93
+
94
+ it "run enqueued task" do
95
+ queue.start!
96
+
97
+ queue.enqueue(:a, 'foo' => '1')
98
+ queue.enqueue(:a, 'foo' => '2')
99
+ 100.times { break if task_class_a.runs.size == 2; sleep 0.01 }
100
+
101
+ expect(task_class_a.runs.size).to eq 2
102
+ expect(task_class_a.runs[0]['foo']).to eq '1'
103
+ expect(task_class_a.runs[1]['foo']).to eq '2'
104
+ end
105
+
106
+ describe "#working?" do
107
+ it "returns true if there're any working tasks" do
108
+ queue.start!
109
+
110
+ expect(queue).not_to be_working
111
+
112
+ queue.enqueue(:a, 'wait' => true, 'id' => 1)
113
+ 100.times { break unless task_class_a.locks.empty?; sleep 0.01 }
114
+ expect(task_class_a.locks).not_to be_empty
115
+ expect(queue).to be_working
116
+
117
+ task_class_a.locks.values.last << true
118
+
119
+ 100.times { break unless queue.working?; sleep 0.01 }
120
+ expect(queue).not_to be_working
121
+ end
122
+ end
123
+
124
+
125
+ describe "#status" do
126
+ it "shows status" do
127
+ queue.start!
128
+
129
+ expect(queue.status[:a][:working]).to be_nil
130
+ expect(queue.status[:a][:queue]).to be_a_kind_of(Array)
131
+ expect(queue.status[:a][:queue]).to be_empty
132
+
133
+ queue.enqueue(:a, 'wait' => true, 'id' => 1)
134
+
135
+ 100.times { break unless task_class_a.locks.empty?; sleep 0.01 }
136
+ expect(task_class_a.locks).not_to be_empty
137
+ expect(queue.status[:a][:working]).to eq('wait' => true, 'id' => 1)
138
+
139
+ queue.enqueue(:a, 'id' => 2)
140
+ 100.times { break unless queue.status[:a][:queue].empty?; sleep 0.01 }
141
+ expect(queue.status[:a][:queue].size).to eq 1
142
+ expect(queue.status[:a][:queue].first).to eq('id' => 2)
143
+
144
+ task_class_a.locks.values.last << true
145
+
146
+ 100.times { break unless queue.status[:a][:working]; sleep 0.01 }
147
+ expect(queue.status[:a][:working]).to be_nil
148
+ expect(queue.status[:a][:queue]).to be_empty
149
+ end
150
+ end
151
+
152
+ context "with multiple task classes" do
153
+ it "run enqueued task" do
154
+ queue.start!
155
+
156
+ queue.enqueue(:a, 'foo' => '1')
157
+ queue.enqueue(:b, 'foo' => '2')
158
+ 100.times { break if task_class_a.runs.size == 1 && task_class_b.runs.size == 1; sleep 0.01 }
159
+
160
+ expect(task_class_a.runs.size).to eq 1
161
+ expect(task_class_b.runs.size).to eq 1
162
+ expect(task_class_a.runs[0]['foo']).to eq '1'
163
+ expect(task_class_b.runs[0]['foo']).to eq '2'
164
+ end
165
+
166
+ it "run enqueued task parallel" do
167
+ queue.start!
168
+
169
+ queue.enqueue(:a, 'foo' => '1', 'wait' => true)
170
+ queue.enqueue(:b, 'foo' => '2', 'wait' => true)
171
+ 100.times { break if task_class_a.locks.size == 1 && task_class_b.locks.size == 1; sleep 0.01 }
172
+
173
+ expect(task_class_a.locks.size).to eq 1
174
+ expect(task_class_b.locks.size).to eq 1
175
+ task_class_a.locks.each_value.first << true
176
+ task_class_b.locks.each_value.first << true
177
+
178
+ expect(task_class_a.runs.size).to eq 1
179
+ expect(task_class_b.runs.size).to eq 1
180
+ expect(task_class_a.runs[0]['foo']).to eq '1'
181
+ expect(task_class_b.runs[0]['foo']).to eq '2'
182
+ end
183
+
184
+ describe "#status" do
185
+ it "shows status for each task class" do
186
+ queue.start!
187
+
188
+ expect(queue.status[:a][:working]).to be_nil
189
+ expect(queue.status[:a][:queue]).to be_a_kind_of(Array)
190
+ expect(queue.status[:a][:queue]).to be_empty
191
+
192
+ expect(queue.status[:b][:working]).to be_nil
193
+ expect(queue.status[:b][:queue]).to be_a_kind_of(Array)
194
+ expect(queue.status[:b][:queue]).to be_empty
195
+
196
+ queue.enqueue(:a, 'wait' => true, 'id' => 1)
197
+ queue.enqueue(:b, 'wait' => true, 'id' => 2)
198
+
199
+ 100.times { break if !task_class_a.locks.empty? && !task_class_a.locks.empty?; sleep 0.01 }
200
+ expect(task_class_a.locks).not_to be_empty
201
+ expect(task_class_b.locks).not_to be_empty
202
+
203
+ expect(queue.status[:a][:working]).to eq('wait' => true, 'id' => 1)
204
+ expect(queue.status[:b][:working]).to eq('wait' => true, 'id' => 2)
205
+
206
+ queue.enqueue(:a, 'id' => 3)
207
+ queue.enqueue(:b, 'id' => 4)
208
+ 100.times { break if !queue.status[:a][:queue].empty? && !queue.status[:b][:queue].empty?; sleep 0.01 }
209
+ expect(queue.status[:a][:queue].size).to eq 1
210
+ expect(queue.status[:a][:queue].first).to eq('id' => 3)
211
+ expect(queue.status[:b][:queue].size).to eq 1
212
+ expect(queue.status[:b][:queue].first).to eq('id' => 4)
213
+
214
+ task_class_a.locks.values.last << true
215
+ task_class_b.locks.values.last << true
216
+
217
+ 100.times { break if !queue.status[:a][:working] && !queue.status[:b][:working]; sleep 0.01 }
218
+ expect(queue.status[:a][:working]).to be_nil
219
+ expect(queue.status[:a][:queue]).to be_empty
220
+ expect(queue.status[:b][:working]).to be_nil
221
+ expect(queue.status[:b][:queue]).to be_empty
222
+
223
+ end
224
+ end
225
+
226
+ describe "#working?" do
227
+ it "returns true if there're any working tasks" do
228
+ queue.start!
229
+
230
+ expect(queue).not_to be_working
231
+
232
+ queue.enqueue(:a, 'wait' => true, 'id' => 1)
233
+ 100.times { break unless task_class_a.locks.empty?; sleep 0.01 }
234
+ expect(task_class_a.locks).not_to be_empty
235
+ expect(queue).to be_working
236
+
237
+ task_class_a.locks.values.last << true
238
+
239
+ 100.times { break unless queue.working?; sleep 0.01 }
240
+ expect(queue).not_to be_working
241
+ end
242
+ end
243
+
244
+ end
245
+ end
246
+ end