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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 37666b908bc503abe07629a7308eeb762be79cf7
4
- data.tar.gz: aef8d218df47dcdb03e5ff069d7e3a5c4a78a13b
3
+ metadata.gz: e7ed13e8778df9ab9ce99e885013120a638a2bb6
4
+ data.tar.gz: 4d85eca80055453cc3aac803a6dc5c0d232d2d38
5
5
  SHA512:
6
- metadata.gz: ada7d2968785edd0b9b1f51dfa53d4d8da428ca87f3e85562d05a0505553fe16b303e60d07d6478e090ff7183996a4dcc16322ac21f70038f61e181dd7d31efa
7
- data.tar.gz: 044f922700e0f5d873058564b263108ef3ee7350084a5561e17550c205f45b37526ef0e1b347ad3788bb51ba080db71e1b32814b2708310e33c9e9b23367f913
6
+ metadata.gz: efef0fb46485a44362ae151ad79eaf6baec6ade4877bf02588b802fe553e1b2115408fda4fbf5628c77ba2d141391bfc1742341d43bfea3d76b6a2a9ee51fa58
7
+ data.tar.gz: c8dc1424d06eda65ce392f5d8e3acd3fa7f667c0d29c1bb6993a3cad27e099c5da2ffa97b3b573fdab045e957c060edbabdc200d1cbb1ae60d3297404f669f2f
data/README.md CHANGED
@@ -34,6 +34,24 @@ Or install it yourself as:
34
34
 
35
35
  TODO: Write usage instructions here
36
36
 
37
+ ## Upgrade Notes
38
+
39
+ ### 0.0.1.alpha20
40
+
41
+ _tl;dr_ Don't mix alpha19 and alpha20.
42
+
43
+ #### Internal component for distribution has been replaced completely
44
+
45
+ alpha20 introduces new class `TaskQueue` and removes `Fetcher`. This changes way to distribute packages -- including internal serf events, job tracking that Distribution API does, etc.
46
+ So basically there's no compatibility for distribution, between alpha19 and alpha20 and later. Distribute task from alpha20 doesn't effect to alpha19, and vice versa.
47
+
48
+ Good new: There's no change in Distribution API.
49
+
50
+ #### Agent status has changed
51
+
52
+ - Due to removal of `Fetcher`, alpha20 removes `.fetcher` object from agent status.
53
+ - Added `.queues`, represents task queues that managed by `TaskQueue` class.
54
+
37
55
  ## Contributing
38
56
 
39
57
  1. Fork it ( http://github.com/sorah/mamiya/fork )
data/lib/mamiya/agent.rb CHANGED
@@ -5,15 +5,17 @@ require 'mamiya/version'
5
5
  require 'mamiya/logger'
6
6
 
7
7
  require 'mamiya/steps/fetch'
8
- require 'mamiya/agent/fetcher'
8
+ require 'mamiya/agent/task_queue'
9
9
 
10
- require 'mamiya/agent/handlers/fetch'
10
+ require 'mamiya/agent/tasks/fetch'
11
+ require 'mamiya/agent/tasks/clean'
12
+
13
+ require 'mamiya/agent/handlers/task'
11
14
  require 'mamiya/agent/actions'
12
15
 
13
16
  module Mamiya
14
17
  class Agent
15
18
  include Mamiya::Agent::Actions
16
- FETCH_REMOVE_EVENT = 'mamiya:fetch-result:remove'
17
19
 
18
20
  def initialize(config, logger: Mamiya::Logger.new, events_only: nil)
19
21
  @config = config
@@ -27,10 +29,11 @@ module Mamiya
27
29
 
28
30
  attr_reader :config, :serf, :logger
29
31
 
30
- def fetcher
31
- @fetcher ||= Mamiya::Agent::Fetcher.new(config, logger: logger).tap do |f|
32
- f.cleanup_hook = self.method(:cleanup_handler)
33
- end
32
+ def task_queue
33
+ @task_queue ||= Mamiya::Agent::TaskQueue.new(self, logger: logger, task_classes: [
34
+ Mamiya::Agent::Tasks::Fetch,
35
+ Mamiya::Agent::Tasks::Clean,
36
+ ])
34
37
  end
35
38
 
36
39
  def run!
@@ -53,19 +56,18 @@ module Mamiya
53
56
 
54
57
  def start
55
58
  serf_start
56
- fetcher_start
59
+ task_queue_start
57
60
  end
58
61
 
59
62
  def terminate
60
63
  serf.stop!
61
- fetcher.stop!
64
+ task_queue.stop!
62
65
  ensure
63
66
  @terminate = false
64
67
  end
65
68
 
66
69
  def update_tags!
67
70
  serf.tags['mamiya'] = ','.tap do |status|
68
- status.concat('fetching,') if fetcher.working?
69
71
  status.concat('ready,') if status == ','
70
72
  end
71
73
 
@@ -80,11 +82,7 @@ module Mamiya
80
82
  s[:name] = serf.name
81
83
  s[:version] = Mamiya::VERSION
82
84
 
83
- s[:fetcher] = {
84
- fetching: fetcher.current_job,
85
- pending: fetcher.queue_size,
86
- pending_jobs: fetcher.pending_jobs.map{ |_| _[0,2] },
87
- }
85
+ s[:queues] = task_queue.status
88
86
 
89
87
  s[:packages] = self.existing_packages
90
88
  end
@@ -123,13 +121,14 @@ module Mamiya
123
121
  name = "mamiya:#{type}"
124
122
  name << ":#{action}" if action
125
123
 
126
- serf.event(name, payload.to_json, coalesce: coalesce)
124
+ serf.event(name, payload.merge(name: self.serf.name).to_json, coalesce: coalesce)
127
125
  end
128
126
 
129
127
  private
130
128
 
131
129
  def init_serf
132
130
  agent_config = (config[:serf] && config[:serf][:agent]) || {}
131
+ # agent_config.merge!(log: $stderr)
133
132
  Villein::Agent.new(**agent_config).tap do |serf|
134
133
  serf.on_user_event do |event|
135
134
  user_event_handler(event)
@@ -151,10 +150,9 @@ module Mamiya
151
150
  logger.debug "Serf became ready"
152
151
  end
153
152
 
154
- def fetcher_start
155
- logger.debug "Starting fetcher"
156
-
157
- fetcher.start!
153
+ def task_queue_start
154
+ logger.debug "Starting task_queue"
155
+ task_queue.start!
158
156
  end
159
157
 
160
158
  def user_event_handler(event)
@@ -174,9 +172,14 @@ module Mamiya
174
172
 
175
173
  if Handlers.const_defined?(class_name)
176
174
  handler = Handlers.const_get(class_name).new(self, event)
177
- handler.send(action || :run!)
175
+ meth = action || :run!
176
+ if handler.respond_to?(meth)
177
+ handler.send meth
178
+ else
179
+ logger.debug "Handler #{class_name} doesn't respond to #{meth}, skipping"
180
+ end
178
181
  else
179
- logger.warn("Discarded event[#{event.user_event}] because we don't handle it")
182
+ #logger.warn("Discarded event[#{event.user_event}] because we don't handle it")
180
183
  end
181
184
  rescue Exception => e
182
185
  logger.fatal("Error during handling event: #{e.inspect}")
@@ -188,13 +191,5 @@ module Mamiya
188
191
  rescue JSON::ParserError
189
192
  logger.warn("Discarded event[#{event.user_event}] with invalid payload (unable to parse as json)")
190
193
  end
191
-
192
- def cleanup_handler(app, package)
193
- trigger('fetch-result', action: 'remove', coalesce: false,
194
- name: self.serf.name,
195
- application: app,
196
- package: package,
197
- )
198
- end
199
194
  end
200
195
  end
@@ -1,14 +1,17 @@
1
1
  module Mamiya
2
2
  class Agent
3
3
  module Actions
4
- # XXX: dupe?
5
- def distribute(application, package)
6
- trigger('fetch',
7
- application: application,
8
- package: package,
9
- coalesce: false
4
+ def order_task(task, coalesce: false, **payload)
5
+ trigger('task',
6
+ coalesce: coalesce,
7
+ task: task,
8
+ **payload,
10
9
  )
11
10
  end
11
+
12
+ def distribute(application, package)
13
+ order_task('fetch', app: application, pkg: package)
14
+ end
12
15
  end
13
16
  end
14
17
  end
@@ -0,0 +1,13 @@
1
+ require 'mamiya/agent/handlers/abstract'
2
+
3
+ module Mamiya
4
+ class Agent
5
+ module Handlers
6
+ class Task < Abstract
7
+ def run!
8
+ agent.task_queue.enqueue(payload['task'].to_sym, payload)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,151 @@
1
+ require 'mamiya/agent'
2
+ require 'thread'
3
+
4
+ # XXX: TODO: have to refactor
5
+ module Mamiya
6
+ class Agent
7
+ class TaskQueue
8
+ GRACEFUL_TIMEOUT = 30
9
+ JOIN_TIMEOUT = 30
10
+
11
+ def initialize(agent, task_classes: [], logger: Mamiya::Logger.new)
12
+ @agent = agent
13
+ @task_classes = task_classes
14
+ @external_queue = Queue.new
15
+ @queues = {}
16
+ @worker_threads = nil
17
+ @statuses = nil
18
+ @queueing_thread = nil
19
+ @lifecycle_mutex = Mutex.new
20
+ @terminate = false
21
+ @logger = logger['task_queue']
22
+ end
23
+
24
+ attr_reader :worker_threads, :task_classes, :agent
25
+
26
+ def start!
27
+ @lifecycle_mutex.synchronize do
28
+ return if running?
29
+
30
+ worker_threads = {}
31
+ queues = {}
32
+ statuses = {}
33
+
34
+ @task_classes.each { |klass|
35
+ name = klass.identifier.to_sym
36
+ queue = queues[name] = Queue.new
37
+ statuses[name] = {pending: [], lock: Mutex.new}
38
+ th = worker_threads[name] = Thread.new(
39
+ klass, queue,
40
+ statuses[name],
41
+ &method(:worker_loop)
42
+ )
43
+ th.abort_on_exception = true
44
+ }
45
+
46
+ @terminate = false
47
+ @statuses = statuses
48
+ @queues = queues
49
+ exqueue = @external_queue = Queue.new
50
+ @queueing_thread = Thread.new(queues, exqueue, statuses, &method(:queueing_loop))
51
+ @queueing_thread.abort_on_exception = true
52
+ @worker_threads = worker_threads
53
+ end
54
+ end
55
+
56
+ def stop!(graceful = false)
57
+ @lifecycle_mutex.synchronize do
58
+ return unless running?
59
+ @terminate = true
60
+ @queueing_thread.kill if @queueing_thread.alive?
61
+ if graceful
62
+ @worker_threads.each do |th|
63
+ th.join(GRACEFUL_TIMEOUT)
64
+ end
65
+ end
66
+ @worker_threads.each do |name, th|
67
+ next unless th.alive?
68
+ th.kill
69
+ th.join(JOIN_TIMEOUT)
70
+ end
71
+ @queues = nil
72
+ @worker_threads = nil
73
+ end
74
+ end
75
+
76
+ def running?
77
+ @worker_threads && !@terminate
78
+ end
79
+
80
+ def working?
81
+ running? && status.any? { |name, stat| stat[:working] }
82
+ end
83
+
84
+ def enqueue(task_name, task)
85
+ raise Stopped, 'this task queue is stopped' unless running?
86
+
87
+ @logger.debug "enqueue #{task_name.inspect}, #{task.inspect}, #{@external_queue.inspect}"
88
+ @external_queue << [task_name, task]
89
+ self
90
+ end
91
+
92
+ def status
93
+ return nil unless running?
94
+ Hash[@statuses.map do |name, st|
95
+ [name, {
96
+ queue: st[:pending].dup,
97
+ working: st[:working] ? st[:working].dup : nil,
98
+ }]
99
+ end]
100
+ end
101
+
102
+ private
103
+
104
+ def worker_loop(task_class, queue, status)
105
+ while task = queue.pop
106
+ break if @terminate
107
+ begin
108
+ status[:lock].synchronize do
109
+ status[:pending].delete task
110
+ status[:working] = task
111
+ end
112
+ task_class.new(self, task, agent: @agent, logger: @logger).execute
113
+ rescue Exception => e
114
+ @logger.error "#{task_class} worker catched error: #{e}\n\t#{e.backtrace.join("\n\t")}"
115
+ ensure
116
+ status[:lock].synchronize do
117
+ status[:working] = nil
118
+ end
119
+ end
120
+ break if @terminate
121
+ end
122
+ end
123
+
124
+ def queueing_loop(queues, external_queue, statuses)
125
+ @logger.debug "queueing thread started #{external_queue.inspect}"
126
+ while _ = external_queue.pop
127
+ task_name, task = _
128
+
129
+ break if @terminate
130
+
131
+ queue = queues[task_name]
132
+ unless queue
133
+ @logger.debug "Ignoring task #{task_name} (queue not defined)"
134
+ next
135
+ end
136
+
137
+ statuses[task_name][:lock].synchronize do
138
+ statuses[task_name][:pending] << task
139
+ end
140
+ @logger.info "Queueing task #{task_name}: #{task.inspect}"
141
+ queue << task
142
+ break if @terminate
143
+ end
144
+ @logger.debug "queueing thread finish"
145
+ rescue Exception => e
146
+ @logger.error "queueing thread error #{e.inspect}\n\t#{e.backtrace.join("\n\t")}"
147
+ raise e
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,61 @@
1
+ require 'mamiya/logger'
2
+
3
+ module Mamiya
4
+ class Agent
5
+ module Tasks
6
+ class Abstract
7
+ def initialize(task_queue, task, agent: nil, logger: Mamiya::Logger.new, raise_error: false)
8
+ @agent = agent
9
+ @task_queue = task_queue
10
+ @task = task.merge('task' => self.class.identifier)
11
+ @error = nil
12
+ @raise_error = raise_error
13
+ @logger = logger["#{self.class.identifier}:#{self.task_id}"]
14
+ end
15
+
16
+ def self.identifier
17
+ self.name.split(/::/).last.gsub(/(.)([A-Z])/, '\1_\2').downcase
18
+ end
19
+
20
+ attr_reader :task, :error, :logger, :agent, :task_queue
21
+
22
+ def raise_error?
23
+ !!@raise_error
24
+ end
25
+
26
+ def task_id
27
+ task['id'] || "0x#{self.__id__.to_s(16)}"
28
+ end
29
+
30
+ def execute
31
+ before
32
+ run
33
+ rescue Exception => error
34
+ @error = error
35
+ raise if raise_error?
36
+ errored
37
+ ensure
38
+ after
39
+ end
40
+
41
+ def before
42
+ end
43
+
44
+ def run
45
+ end
46
+
47
+ def after
48
+ end
49
+
50
+ def errored
51
+ end
52
+
53
+ private
54
+
55
+ def config
56
+ @config ||= agent ? agent.config : nil
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,44 @@
1
+ require 'mamiya/agent/tasks/abstract'
2
+
3
+ module Mamiya
4
+ class Agent
5
+ module Tasks
6
+ class Clean < Abstract
7
+
8
+ def run
9
+ victims.each do |app, victim|
10
+ @logger.info "Cleaning up: remove #{victim}"
11
+ File.unlink(victim) if File.exist?(victim)
12
+
13
+ meta_victim = victim.sub(/\.tar\.gz\z/, '.json')
14
+ if File.exist?(meta_victim)
15
+ @logger.info "Cleaning up: remove #{meta_victim}"
16
+ File.unlink(meta_victim)
17
+ end
18
+
19
+ package_name = File.basename(victim, '.tar.gz')
20
+
21
+ # XXX: depends on FS structure
22
+ agent.trigger('pkg', action: 'remove',
23
+ application: app,
24
+ package: package_name,
25
+ coalesce: false,
26
+ )
27
+ end
28
+ end
29
+
30
+ def victims
31
+ Dir[File.join(config[:packages_dir], '*')].flat_map do |app|
32
+ packages = Dir[File.join(app, "*.tar.gz")].
33
+ sort_by { |_| [File.mtime(_), _] }
34
+
35
+ packages[0...-(config[:keep_packages])].map do |victim|
36
+ [File.basename(app), victim]
37
+ end
38
+ end
39
+ end
40
+
41
+ end
42
+ end
43
+ end
44
+ end