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.
@@ -1,165 +0,0 @@
1
- require 'thread'
2
- require 'mamiya/steps/fetch'
3
-
4
- require 'mamiya/storages/abstract'
5
-
6
- module Mamiya
7
- class Agent
8
- ##
9
- # This class has a queue for fetching packages.
10
- class Fetcher
11
- GRACEFUL_TIMEOUT = 60
12
-
13
- def initialize(config, logger: Mamiya::Logger.new)
14
- @worker_thread = nil
15
- @queueing_thread = nil
16
- @external_queue = Queue.new
17
- @internal_queue = Queue.new
18
-
19
- @config = config
20
- @destination = config[:packages_dir]
21
- @keep_packages = config[:keep_packages]
22
- @current_job = nil
23
- @pending_jobs = []
24
-
25
- @logger = logger['fetcher']
26
- end
27
-
28
- attr_reader :worker_thread
29
- attr_reader :queueing_thread
30
- attr_reader :current_job
31
- attr_reader :pending_jobs
32
- attr_writer :cleanup_hook
33
-
34
- def enqueue(app, package, before: nil, &callback)
35
- @external_queue << [app, package, before, callback]
36
- end
37
-
38
- def queue_size
39
- @internal_queue.size
40
- end
41
-
42
- def start!
43
- stop!
44
- @logger.info 'Starting...'
45
-
46
- @worker_thread = Thread.new(&method(:main_loop))
47
- @worker_thread.abort_on_exception = true
48
-
49
- @queueing_thread = Thread.new(&method(:queueing_loop))
50
- @queueing_thread.abort_on_exception = true
51
- end
52
-
53
- def stop!(graceful = false)
54
- {@external_queue => @queueing_thread, @internal_queue => @worker_thread}.each do |q, th|
55
- next unless th
56
- if graceful
57
- q << :suicide
58
- th.join(GRACEFUL_TIMEOUT)
59
- end
60
-
61
- th.kill if th.alive?
62
- end
63
- ensure
64
- @worker_thread = nil
65
- @queueing_thread = nil
66
- end
67
-
68
- def running?
69
- @worker_thread && @worker_thread.alive? && \
70
- @queueing_thread && @queueing_thread.alive?
71
- end
72
-
73
- def working?
74
- !!@current_job
75
- end
76
-
77
- def cleanup
78
- Dir[File.join(@destination, '*')].each do |app|
79
- packages = Dir[File.join(app, "*.tar.gz")]
80
- packages.sort_by! { |_| [File.mtime(_), _] }
81
- packages[0...-@keep_packages].each do |victim|
82
- @logger.info "Cleaning up: remove #{victim}"
83
- File.unlink(victim) if File.exist?(victim)
84
-
85
- meta_victim = victim.sub(/\.tar\.gz\z/, '.json')
86
- if File.exist?(meta_victim)
87
- @logger.info "Cleaning up: remove #{meta_victim}"
88
- File.unlink(meta_victim)
89
- end
90
-
91
- package_name = File.basename(victim, '.tar.gz')
92
- if @cleanup_hook
93
- @cleanup_hook.call(File.basename(app), package_name)
94
- end
95
- end
96
- end
97
- end
98
-
99
- private
100
-
101
- def main_loop
102
- while order = @internal_queue.pop
103
- break if order == :suicide
104
- @pending_jobs.delete(order)
105
- handle_order(*order)
106
- end
107
- end
108
-
109
- def queueing_loop
110
- while order = @external_queue.pop
111
- break if order == :suicide
112
- @pending_jobs << order
113
- @internal_queue << order
114
- end
115
- end
116
-
117
- def handle_order(app, package, before_hook = nil, callback = nil)
118
- @current_job = [app, package]
119
- @logger.info "fetching #{app}:#{package}"
120
-
121
- if @config[:fetch_sleep]
122
- wait = rand(@config[:fetch_sleep])
123
- @logger.debug "Sleeping #{wait} before starting fetch"
124
- sleep wait
125
- end
126
-
127
- # TODO: Limit apps by configuration
128
-
129
- destination = File.join(@destination, app)
130
-
131
- Dir.mkdir(destination) unless File.exist?(destination)
132
-
133
- before_hook.call if before_hook
134
-
135
- # TODO: before run hook for agent.update_tags!
136
- Mamiya::Steps::Fetch.new(
137
- application: app,
138
- package: package,
139
- destination: destination,
140
- config: @config,
141
- ).run!
142
-
143
- @current_job = nil
144
- callback.call if callback
145
-
146
- @logger.info "fetched #{app}:#{package}"
147
-
148
- cleanup
149
-
150
- rescue Mamiya::Storages::Abstract::AlreadyFetched => e
151
- @logger.info "skipped #{app}:#{package} (already fetched)"
152
- callback.call(e) if callback
153
- rescue Exception => e
154
- @logger.fatal "fetch failed (#{app}:#{package}): #{e.inspect}"
155
- e.backtrace.each do |line|
156
- @logger.fatal "\t#{line}"
157
- end
158
-
159
- callback.call(e) if callback
160
- ensure
161
- @current_job = nil
162
- end
163
- end
164
- end
165
- end
@@ -1,78 +0,0 @@
1
- require 'mamiya/agent/handlers/abstract'
2
- require 'mamiya/storages/abstract'
3
-
4
- module Mamiya
5
- class Agent
6
- module Handlers
7
- class Fetch < Abstract
8
- FETCH_ACK_EVENT = 'mamiya:fetch-result:ack'
9
- FETCH_START_EVENT = 'mamiya:fetch-result:start'
10
- FETCH_SUCCESS_EVENT = 'mamiya:fetch-result:success'
11
- FETCH_ERROR_EVENT = 'mamiya:fetch-result:error'
12
-
13
- IGNORED_ERRORS = [
14
- Mamiya::Storages::Abstract::AlreadyFetched.new(''),
15
- ].freeze
16
-
17
- def run!
18
- # XXX: Why don't use agent#trigger ...
19
- agent.serf.event(FETCH_ACK_EVENT,
20
- {
21
- name: agent.serf.name,
22
- application: payload['application'],
23
- package: payload['package'],
24
- pending: agent.fetcher.queue_size.succ,
25
- }.to_json,
26
- coalesce: false,
27
- )
28
-
29
- agent.fetcher.enqueue(
30
- payload['application'], payload['package'],
31
- before: proc {
32
- agent.serf.event(FETCH_START_EVENT,
33
- {
34
- name: agent.serf.name,
35
- application: payload['application'],
36
- package: payload['package'],
37
- pending: agent.fetcher.queue_size.succ,
38
- }.to_json,
39
- coalesce: false,
40
- )
41
- agent.update_tags!
42
- }
43
- ) do |error|
44
- if error && IGNORED_ERRORS.lazy.grep(error.class).none?
45
- # FIXME: TODO: may exceed 256
46
- begin
47
- agent.serf.event(FETCH_ERROR_EVENT,
48
- {
49
- name: agent.serf.name,
50
- application: payload['application'],
51
- package: payload['package'],
52
- error: error.class,
53
- pending: agent.fetcher.queue_size,
54
- }.to_json,
55
- coalesce: false,
56
- )
57
- rescue Villein::Client::SerfError => e
58
- agent.logger.error "error sending fetch error event: #{e.inspect}"
59
- end
60
- else
61
- agent.serf.event(FETCH_SUCCESS_EVENT,
62
- {
63
- name: agent.serf.name,
64
- application: payload['application'],
65
- package: payload['package'],
66
- pending: agent.fetcher.queue_size,
67
- }.to_json,
68
- coalesce: false,
69
- )
70
- end
71
-
72
- agent.update_tags!
73
- end
74
- end
75
- end
76
- end
77
- end
78
- end
@@ -1,237 +0,0 @@
1
- require 'spec_helper'
2
- require 'pathname'
3
- require 'tmpdir'
4
- require 'fileutils'
5
-
6
- require 'mamiya/agent/fetcher'
7
- require 'mamiya/steps/fetch'
8
-
9
-
10
- describe Mamiya::Agent::Fetcher do
11
- let!(:tmpdir) { Dir.mktmpdir('mamiya-agent-fetcher-spec') }
12
- after { FileUtils.remove_entry_secure(tmpdir) if File.exist?(tmpdir) }
13
-
14
- let(:config) do
15
- {packages_dir: tmpdir, keep_packages: 2}
16
- end
17
-
18
- subject(:fetcher) { described_class.new(config) }
19
-
20
- describe "lifecycle" do
21
- it "can start and stop" do
22
- expect(fetcher.worker_thread).to be_nil
23
- expect(fetcher).not_to be_running
24
-
25
- fetcher.start!
26
-
27
- expect(fetcher).to be_running
28
- expect(fetcher.worker_thread).to be_a(Thread)
29
- expect(fetcher.worker_thread).to be_alive
30
- th = fetcher.worker_thread
31
-
32
- fetcher.stop!
33
-
34
- 20.times { break unless th.alive?; sleep 0.1 }
35
- expect(th).not_to be_alive
36
-
37
- expect(fetcher.worker_thread).to be_nil
38
- expect(fetcher).not_to be_running
39
- end
40
-
41
- it "can graceful stop"
42
- end
43
-
44
- describe "#cleanup" do
45
- before do
46
- path = Pathname.new(tmpdir)
47
-
48
- path.join('a').mkdir
49
- File.write path.join('a', "a.tar.gz"), "\n"
50
- File.write path.join('a', "a.json"), "\n"
51
- File.write path.join('a', "b.json"), "\n"
52
- File.write path.join('a', "b.tar.gz"), "\n"
53
- File.write path.join('a', "c.json"), "\n"
54
- File.write path.join('a', "c.tar.gz"), "\n"
55
- path.join('b').mkdir
56
- File.write path.join('b', "a.tar.gz"), "\n"
57
- File.write path.join('b', "a.json"), "\n"
58
-
59
- path.join('c').mkdir
60
- File.write path.join('c', "a.tar.gz"), "\n"
61
- File.write path.join('c', "b.json"), "\n"
62
- end
63
-
64
- it "cleans up" do
65
- called = []
66
- fetcher.cleanup_hook = proc { |a,b| called << [a,b] }
67
-
68
- fetcher.cleanup
69
-
70
- path = Pathname.new(tmpdir)
71
- existences = Hash[
72
- [
73
- path.join('a', 'a.tar.gz'),
74
- path.join('a', 'a.json'),
75
- path.join('a', 'b.tar.gz'),
76
- path.join('a', 'b.json'),
77
- path.join('a', 'c.tar.gz'),
78
- path.join('a', 'c.json'),
79
- ].map { |file|
80
- [file, file.exist?]
81
- }
82
- ]
83
-
84
- expect(called).to eq([['a', 'a']])
85
- expect(existences).to eq(
86
- path.join('a', 'a.tar.gz') => false,
87
- path.join('a', 'a.json') => false,
88
- path.join('a', 'b.tar.gz') => true,
89
- path.join('a', 'b.json') => true,
90
- path.join('a', 'c.tar.gz') => true,
91
- path.join('a', 'c.json') => true,
92
- )
93
- end
94
- end
95
-
96
- describe "#pending_jobs" do
97
- before do
98
- step = double('fetch-step')
99
- allow(step).to receive(:run!)
100
- allow(Mamiya::Steps::Fetch).to receive(:new).with(
101
- application: 'myapp',
102
- package: 'package',
103
- destination: File.join(tmpdir, 'myapp'),
104
- config: config,
105
- ).and_return(step)
106
- end
107
-
108
- it "shows remaining jobs" do
109
- fetcher.start!; fetcher.worker_thread.kill
110
-
111
- expect {
112
- fetcher.enqueue('myapp', 'package')
113
- fetcher.stop!(:graceful)
114
- }.to change { fetcher.pending_jobs } \
115
- .from([]).to([['myapp', 'package', nil, nil]])
116
-
117
- fetcher.start!; fetcher.stop!(:graceful)
118
-
119
- expect(fetcher.pending_jobs).to be_empty
120
- end
121
- end
122
-
123
- describe "mainloop" do
124
- before do
125
- allow(step).to receive(:run!)
126
- allow(Mamiya::Steps::Fetch).to receive(:new).with(
127
- application: 'myapp',
128
- package: 'package',
129
- destination: File.join(tmpdir, 'myapp'),
130
- config: config,
131
- ).and_return(step)
132
-
133
- fetcher.start!
134
- end
135
-
136
- let(:step) { double('fetch-step') }
137
-
138
- it "starts fetch step for each order" do
139
- flag = false
140
-
141
- expect(step).to receive(:run!) do
142
- flag = true
143
- end
144
-
145
- fetcher.enqueue('myapp', 'package')
146
- fetcher.stop!(:graceful)
147
- end
148
-
149
- it "calls callback" do
150
- received = true
151
-
152
- fetcher.enqueue('myapp', 'package') do |succeeded|
153
- expect(fetcher.working?).to be_false
154
- received = succeeded
155
- end
156
-
157
- fetcher.stop!(:graceful)
158
-
159
- expect(received).to be_nil
160
- end
161
-
162
- it "calls cleanup" do
163
- expect(fetcher).to receive(:cleanup)
164
- fetcher.enqueue('myapp', 'package')
165
- fetcher.stop!(:graceful)
166
- end
167
-
168
- it "claims itself as working" do
169
- expect(fetcher.working?).to be_false
170
- expect(fetcher.current_job).to be_nil
171
-
172
- received = false
173
- fetcher.enqueue 'myapp', 'package', before: proc { |error|
174
- expect(fetcher.working?).to be_true
175
- expect(fetcher.current_job).to eq %w(myapp package)
176
- received = true
177
- }
178
-
179
- fetcher.stop!(:graceful)
180
- expect(received).to be_true
181
- expect(fetcher.working?).to be_false
182
- expect(fetcher.current_job).to be_nil
183
- end
184
-
185
- context "with config.fetch_sleep" do
186
- it "calls sleep" do
187
- config[:fetch_sleep] = 1
188
- expect(fetcher).to receive(:sleep)
189
- fetcher.enqueue 'myapp', 'package'
190
- fetcher.stop!(:graceful)
191
- end
192
- end
193
-
194
- context "with before hook" do
195
- it "calls callback" do
196
- run = false
197
- received = false
198
-
199
- allow(step).to receive(:run!) do
200
- run = true
201
- end
202
-
203
- fetcher.enqueue('myapp', 'package', before: proc {
204
- received = true
205
- expect(run).to be_false
206
- })
207
- fetcher.stop!(:graceful)
208
-
209
- expect(received).to be_true
210
- end
211
- end
212
-
213
- context "when fetch step raised error" do
214
- let(:exception) { Exception.new("he he...") }
215
-
216
- before do
217
- allow(step).to receive(:run!).and_raise(exception)
218
- end
219
-
220
- it "calls callback with error" do
221
- received = nil
222
-
223
- fetcher.enqueue('myapp', 'package') do |error|
224
- received = error
225
- end
226
-
227
- fetcher.stop!(:graceful)
228
-
229
- expect(received).to eq exception
230
- end
231
- end
232
-
233
- after do
234
- fetcher.stop!
235
- end
236
- end
237
- end