mamiya 0.0.1.alpha19 → 0.0.1.alpha20

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.
@@ -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