mamiya 0.0.1.alpha24 → 0.0.1.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +24 -18
  3. data/example/config.agent.rb +3 -0
  4. data/example/config.rb +27 -0
  5. data/example/deploy.rb +19 -11
  6. data/lib/mamiya/agent.rb +38 -2
  7. data/lib/mamiya/agent/actions.rb +4 -0
  8. data/lib/mamiya/agent/tasks/clean.rb +35 -0
  9. data/lib/mamiya/agent/tasks/notifyable.rb +4 -1
  10. data/lib/mamiya/agent/tasks/prepare.rb +2 -0
  11. data/lib/mamiya/agent/tasks/switch.rb +123 -0
  12. data/lib/mamiya/cli.rb +1 -20
  13. data/lib/mamiya/cli/client.rb +225 -3
  14. data/lib/mamiya/configuration.rb +18 -0
  15. data/lib/mamiya/master/agent_monitor.rb +19 -4
  16. data/lib/mamiya/master/agent_monitor_handlers.rb +10 -0
  17. data/lib/mamiya/master/application_status.rb +72 -0
  18. data/lib/mamiya/master/package_status.rb +178 -0
  19. data/lib/mamiya/master/web.rb +48 -44
  20. data/lib/mamiya/steps/build.rb +1 -1
  21. data/lib/mamiya/steps/prepare.rb +1 -1
  22. data/lib/mamiya/steps/switch.rb +2 -1
  23. data/lib/mamiya/storages/filesystem.rb +1 -1
  24. data/lib/mamiya/storages/mock.rb +1 -1
  25. data/lib/mamiya/storages/s3.rb +1 -1
  26. data/lib/mamiya/storages/s3_proxy.rb +1 -1
  27. data/lib/mamiya/version.rb +1 -1
  28. data/spec/agent/actions_spec.rb +26 -1
  29. data/spec/agent/tasks/clean_spec.rb +88 -2
  30. data/spec/agent/tasks/notifyable_spec.rb +3 -3
  31. data/spec/agent/tasks/switch_spec.rb +176 -0
  32. data/spec/agent_spec.rb +142 -1
  33. data/spec/configuration_spec.rb +23 -0
  34. data/spec/master/agent_monitor_spec.rb +128 -38
  35. data/spec/master/application_status_spec.rb +171 -0
  36. data/spec/master/package_status_spec.rb +560 -0
  37. data/spec/master/web_spec.rb +116 -1
  38. data/spec/steps/build_spec.rb +1 -1
  39. data/spec/steps/prepare_spec.rb +6 -1
  40. data/spec/steps/switch_spec.rb +6 -2
  41. data/spec/storages/filesystem_spec.rb +2 -21
  42. data/spec/storages/s3_proxy_spec.rb +2 -22
  43. data/spec/storages/s3_spec.rb +2 -20
  44. metadata +11 -2
@@ -0,0 +1,178 @@
1
+ require 'mamiya/master'
2
+
3
+ module Mamiya
4
+ class Master
5
+ ##
6
+ # This class determines distribution and releasing status of given package using given AgentMonitor.
7
+ class PackageStatus
8
+ def initialize(agent_monitor, application, package, labels: nil)
9
+ @application = application
10
+ @package = package
11
+ @agents = agent_monitor.statuses(labels: @labels).reject { |_, s| s['master'] }
12
+ @labels = labels
13
+ end
14
+
15
+ attr_reader :labels, :application, :package, :agents
16
+
17
+ def to_hash
18
+ {
19
+ application: application,
20
+ package: package,
21
+ labels: labels,
22
+ status: status,
23
+ participants_count: participants.size,
24
+ non_participants: non_participants,
25
+ active: current_agents,
26
+ fetch: {
27
+ queued: fetch_queued_agents,
28
+ working: fetching_agents,
29
+ done: fetched_agents,
30
+ },
31
+ prepare: {
32
+ queued: prepare_queued_agents,
33
+ working: preparing_agents,
34
+ done: prepared_agents,
35
+ },
36
+ switch: {
37
+ queued: switch_queued_agents,
38
+ working: switching_agents,
39
+ done: current_agents,
40
+ },
41
+ }
42
+ end
43
+
44
+ def status
45
+ [].tap do |s|
46
+ working = false
47
+
48
+ case
49
+ when fetched_agents == agents.keys
50
+ s << :distributed
51
+ when !fetching_agents.empty? || !fetch_queued_agents.empty?
52
+ working = true
53
+ s << :distributing
54
+ end
55
+ if fetched_agents != agents.keys && !fetched_agents.empty?
56
+ s << :partially_distributed
57
+ end
58
+
59
+ # TODO: FIXME: tests
60
+ case
61
+ when prepared_agents == agents.keys
62
+ s << :prepared
63
+ when !preparing_agents.empty? || !prepare_queued_agents.empty?
64
+ working = true
65
+ s << :preparing
66
+ end
67
+ if prepared_agents != agents.keys && !prepared_agents.empty?
68
+ s << :partially_prepared
69
+ end
70
+
71
+ # TODO: FIXME: tests
72
+ case
73
+ when current_agents == agents.keys
74
+ s << :active
75
+ when !current_agents.empty? || !switch_queued_agents.empty?
76
+ working = true
77
+ s << :switching
78
+ end
79
+ if current_agents != agents.keys && !current_agents.empty?
80
+ s << :partially_active
81
+ end
82
+
83
+ s << :unknown if s.empty?
84
+ s << :working if working
85
+ end
86
+ end
87
+
88
+ def fetch_queued_agents
89
+ @fetch_queued_agents ||= agents.select do |name, agent|
90
+ queue = agent['queues'] && agent['queues']['fetch'] && agent['queues']['fetch']['queue']
91
+ queue && queue.any? { |task|
92
+ app_and_pkg == task.values_at('app', 'pkg')
93
+ }
94
+ end.keys - fetched_agents
95
+ end
96
+
97
+ def fetching_agents
98
+ @fetching_agents ||= agents.select do |name, agent|
99
+ task = agent['queues'] && agent['queues']['fetch'] && agent['queues']['fetch'] && agent['queues']['fetch']['working']
100
+ task && app_and_pkg == task.values_at('app', 'pkg')
101
+ end.keys - fetched_agents
102
+ end
103
+
104
+ def fetched_agents
105
+ @fetched_agents ||= agents.select do |name, agent|
106
+ packages = agent['packages'] && agent['packages'][application]
107
+ packages && packages.include?(package)
108
+ end.keys
109
+ end
110
+
111
+ def prepare_queued_agents
112
+ @prepare_queued_agents ||= agents.select do |name, agent|
113
+ queue = agent['queues'] && agent['queues']['prepare'] && agent['queues']['prepare']['queue']
114
+ queue && queue.any? { |task|
115
+ app_and_pkg == task.values_at('app', 'pkg')
116
+ }
117
+ end.keys - prepared_agents
118
+ end
119
+
120
+ def preparing_agents
121
+ @preparing_agents ||= agents.select do |name, agent|
122
+ task = agent['queues'] && agent['queues']['prepare'] && agent['queues']['prepare'] && agent['queues']['prepare']['working']
123
+ task && app_and_pkg == task.values_at('app', 'pkg')
124
+ end.keys - prepared_agents
125
+ end
126
+
127
+ def prepared_agents
128
+ @prepare_agents ||= agents.select do |name, agent|
129
+ packages = agent['prereleases'] && agent['prereleases'][application]
130
+ packages && packages.include?(package)
131
+ end.keys
132
+ end
133
+
134
+ def switch_queued_agents
135
+ @switch_queued_agents ||= agents.select do |name, agent|
136
+ queue = agent['queues'] && agent['queues']['switch'] && agent['queues']['switch']['queue']
137
+ queue && queue.any? { |task|
138
+ app_and_pkg == task.values_at('app', 'pkg')
139
+ }
140
+ end.keys - current_agents
141
+ end
142
+
143
+ def switching_agents
144
+ @switching_agents ||= agents.select do |name, agent|
145
+ task = agent['queues'] && agent['queues']['switch'] && agent['queues']['switch'] && agent['queues']['switch']['working']
146
+ task && app_and_pkg == task.values_at('app', 'pkg')
147
+ end.keys - current_agents
148
+ end
149
+
150
+ def current_agents
151
+ @current_agents ||= agents.select do |name, agent|
152
+ current = agent['currents'] && agent['currents'][application]
153
+ current == package
154
+ end.keys
155
+ end
156
+
157
+ def participants
158
+ (fetch_queued_agents + fetching_agents + fetched_agents + \
159
+ prepare_queued_agents + preparing_agents + prepared_agents + \
160
+ switch_queued_agents + switching_agents + current_agents).uniq
161
+ end
162
+
163
+ def non_participants
164
+ agents.keys - participants
165
+ end
166
+
167
+ def reload
168
+ @agents = nil
169
+ end
170
+
171
+ private
172
+
173
+ def app_and_pkg
174
+ @app_and_pkg ||= [application, package]
175
+ end
176
+ end
177
+ end
178
+ end
@@ -40,6 +40,20 @@ module Mamiya
40
40
  "mamiya v#{Mamiya::VERSION}\n"
41
41
  end
42
42
 
43
+ get '/applications/:application/status' do
44
+ content_type :json
45
+
46
+ expr = params[:labels] && parse_label_matcher_expr(params[:labels])
47
+ appstatus = agent_monitor.application_status(params[:application], labels: expr)
48
+
49
+ if appstatus.participants.empty?
50
+ status 404
51
+ {error: 'not found'}.to_json
52
+ else
53
+ appstatus.to_hash.to_json
54
+ end
55
+ end
56
+
43
57
  get '/packages/:application' do
44
58
  content_type :json
45
59
  packages = storage(params[:application]).packages
@@ -93,8 +107,33 @@ module Mamiya
93
107
  end
94
108
  end
95
109
 
110
+ post '/packages/:application/:package/switch' do
111
+ if storage(params[:application]).meta(params[:package])
112
+ status 204
113
+ master.switch(params[:application], params[:package], labels: params['labels'], no_release: !!(params['no_release'] || params[:no_release]))
114
+ else
115
+ status 404
116
+ content_type :json
117
+ {error: 'not found'}.to_json
118
+ end
119
+ end
120
+
121
+ get '/packages/:application/:package/status' do
122
+ content_type :json
123
+ meta = storage(params[:application]).meta(params[:package])
124
+ unless meta
125
+ status 404
126
+ next {error: 'not found'}.to_json
127
+ end
128
+
129
+ expr = params[:labels] && parse_label_matcher_expr(params[:labels])
130
+ pkgstatus = agent_monitor.package_status(params[:application], params[:package], labels: expr)
131
+
132
+ pkgstatus.to_hash.to_json
133
+ end
134
+
96
135
  get '/packages/:application/:package/distribution' do
97
- # TODO: filter with label
136
+ # TODO: Deprecated. Remove this
98
137
  content_type :json
99
138
  meta = storage(params[:application]).meta(params[:package])
100
139
  unless meta
@@ -102,59 +141,24 @@ module Mamiya
102
141
  next {error: 'not found'}.to_json
103
142
  end
104
143
 
144
+ expr = params[:labels] && parse_label_matcher_expr(params[:labels])
145
+ pkgstatus = agent_monitor.package_status(params[:application], params[:package], labels: expr)
146
+
105
147
  result = {
106
148
  application: params[:application],
107
149
  package: params[:package],
108
- distributed: [],
109
- fetching: [],
110
- queued: [],
111
- not_distributed: []
150
+ distributed: pkgstatus.fetched_agents,
151
+ fetching: pkgstatus.fetching_agents,
152
+ queued: pkgstatus.fetch_queued_agents,
153
+ not_distributed: pkgstatus.non_participants,
112
154
  }
113
- if params[:labels]
114
- expr = parse_label_matcher_expr(params[:labels])
115
- statuses = agent_monitor.statuses(labels: expr)
116
- else
117
- statuses = agent_monitor.statuses
118
- end
119
-
120
- pkg_array = [params[:application], params[:package]]
121
-
122
- statuses.each do |name, status|
123
- next if status["master"]
124
- queue = status["queues"] && status["queues"]["fetch"]
125
- packages = status["packages"] && status["packages"][params[:application]]
126
-
127
- task_matcher = -> (task) do
128
- task["task"] == "fetch" &&
129
- task["app"] == params[:application] &&
130
- task["pkg"] == params[:package]
131
- end
132
-
133
-
134
- case
135
- when packages && packages.include?(params[:package])
136
-
137
- result[:distributed] << name
138
-
139
- when queue && queue["working"] && task_matcher.call(queue['working'])
140
-
141
- result[:fetching] << name
142
-
143
- when queue['queue'].any?(&task_matcher)
144
-
145
- result[:queued] << name
146
-
147
- else
148
- result[:not_distributed] << name
149
- end
150
- end
151
155
 
152
156
  result[:distributed_count] = result[:distributed].size
153
157
  result[:fetching_count] = result[:fetching].size
154
158
  result[:not_distributed_count] = result[:not_distributed].size
155
159
  result[:queued_count] = result[:queued].size
156
160
 
157
- total = statuses.size
161
+ total = agent_monitor.statuses.size
158
162
 
159
163
  case
160
164
  when 0 < result[:queued_count] || 0 < result[:fetching_count]
@@ -128,7 +128,7 @@ module Mamiya
128
128
  logger.debug "Determining package name..."
129
129
  name = Dir.chdir(script.build_from) {
130
130
  script.package_name[
131
- [Time.now.strftime("%Y-%m-%d_%H.%M.%S"), script.application]
131
+ [Time.now.strftime("%Y%m%d%H%M%S"), script.application]
132
132
  ].join('-')
133
133
  }
134
134
  logger.info "Package name determined: #{name}"
@@ -35,7 +35,7 @@ module Mamiya
35
35
  def script
36
36
  @target_script ||= Mamiya::Script.new.load!(
37
37
  target.join('.mamiya.script', target_meta['script'])).tap do |script|
38
- # XXX: release_path is set by options[:target] but deploy_to is set by script?
38
+ script.set(:deploy_to, config.deploy_to_for(script.application))
39
39
  script.set(:release_path, target)
40
40
  script.set(:logger, logger)
41
41
  end
@@ -45,10 +45,11 @@ module Mamiya
45
45
  # This class see target_dir's script
46
46
  alias given_script script
47
47
 
48
+ # XXX: modulize?
48
49
  def script
49
50
  @target_script ||= Mamiya::Script.new.load!(
50
51
  target.join('.mamiya.script', target_meta['script'])).tap do |script|
51
- # XXX: release_path is set by options[:target] but deploy_to is set by script?
52
+ script.set(:deploy_to, config.deploy_to_for(script.application))
52
53
  script.set(:release_path, target)
53
54
  script.set(:logger, logger)
54
55
  end
@@ -42,7 +42,7 @@ module Mamiya
42
42
  package_path = File.join(destination, "#{package_name}.tar.gz")
43
43
  meta_path = File.join(destination, "#{package_name}.json")
44
44
 
45
- if File.exists?(package_path) || File.exists?(meta_path)
45
+ if File.exists?(package_path) && File.exists?(meta_path)
46
46
  raise AlreadyFetched
47
47
  end
48
48
 
@@ -41,7 +41,7 @@ module Mamiya
41
41
  package_path = File.join(destination, "#{package_name}.tar.gz")
42
42
  meta_path = File.join(destination, "#{package_name}.json")
43
43
 
44
- if File.exists?(package_path) || File.exists?(meta_path)
44
+ if File.exists?(package_path) && File.exists?(meta_path)
45
45
  raise AlreadyFetched
46
46
  end
47
47
 
@@ -48,7 +48,7 @@ module Mamiya
48
48
  package_path = File.join(dir, File.basename(package_key))
49
49
  meta_path = File.join(dir, File.basename(meta_key))
50
50
 
51
- if File.exists?(package_path) || File.exists?(meta_path)
51
+ if File.exists?(package_path) && File.exists?(meta_path)
52
52
  raise AlreadyFetched
53
53
  end
54
54
 
@@ -18,7 +18,7 @@ module Mamiya
18
18
  package_path = File.join(dir, File.basename(package_key))
19
19
  meta_path = File.join(dir, File.basename(meta_key))
20
20
 
21
- if File.exists?(package_path) || File.exists?(meta_path)
21
+ if File.exists?(package_path) && File.exists?(meta_path)
22
22
  raise AlreadyFetched
23
23
  end
24
24
 
@@ -1,3 +1,3 @@
1
1
  module Mamiya
2
- VERSION = "0.0.1.alpha24"
2
+ VERSION = "0.0.1.beta1"
3
3
  end
@@ -36,7 +36,6 @@ describe Mamiya::Agent::Actions do
36
36
  agent.distribute('myapp', 'mypkg', labels: ['foo'])
37
37
  end
38
38
  end
39
-
40
39
  end
41
40
 
42
41
  describe "#prepare" do
@@ -54,4 +53,30 @@ describe Mamiya::Agent::Actions do
54
53
  end
55
54
  end
56
55
  end
56
+
57
+
58
+ describe "#switch" do
59
+ it "sends switch request" do
60
+ expect(agent).to receive(:trigger).with('task', task: 'switch', app: 'myapp', pkg: 'mypkg', coalesce: false, no_release: false)
61
+
62
+ agent.switch('myapp', 'mypkg')
63
+ end
64
+
65
+ context "with no_release" do
66
+ it "sends switch request" do
67
+ expect(agent).to receive(:trigger).with('task', task: 'switch', app: 'myapp', pkg: 'mypkg', coalesce: false, no_release: true)
68
+
69
+ agent.switch('myapp', 'mypkg', no_release: true)
70
+ end
71
+
72
+ end
73
+
74
+ context "with labels" do
75
+ it "adds _labels on task" do
76
+ expect(agent).to receive(:trigger).with('task', task: 'switch', app: 'myapp', pkg: 'mypkg', _labels: ['foo'], coalesce: false, no_release: false)
77
+
78
+ agent.switch('myapp', 'mypkg', labels: ['foo'])
79
+ end
80
+ end
81
+ end
57
82
  end
@@ -3,6 +3,7 @@ require 'spec_helper'
3
3
  require 'mamiya/agent/tasks/clean'
4
4
  require 'mamiya/agent/tasks/abstract'
5
5
  require 'mamiya/steps/fetch'
6
+ require 'mamiya/configuration'
6
7
 
7
8
  describe Mamiya::Agent::Tasks::Clean do
8
9
  let!(:tmpdir) { Dir.mktmpdir('mamiya-agent-tasks-clean-spec') }
@@ -10,10 +11,21 @@ describe Mamiya::Agent::Tasks::Clean do
10
11
 
11
12
  let(:packages_dir) { Pathname.new(tmpdir).join('packages').tap(&:mkdir) }
12
13
  let(:prereleases_dir) { Pathname.new(tmpdir).join('prereleases').tap(&:mkdir) }
14
+
15
+ let(:deploy_to_a) { Pathname.new(tmpdir).join('app_a').tap(&:mkdir) }
16
+ let(:deploy_to_b) { Pathname.new(tmpdir).join('app_b').tap(&:mkdir) }
13
17
 
14
18
  let(:config) do
15
- {packages_dir: packages_dir, keep_packages: 2,
16
- prereleases_dir: prereleases_dir, keep_prereleases: 2,}
19
+ Mamiya::Configuration.new.tap do |c|
20
+ c.set :packages_dir, packages_dir
21
+ c.set :prereleases_dir, prereleases_dir
22
+ c.set :keep_packages, 2
23
+ c.set :keep_prereleases, 2
24
+ c.set :keep_releases, 2
25
+
26
+ c.applications[:app_a] = {deploy_to: deploy_to_a}
27
+ c.applications[:app_b] = {deploy_to: deploy_to_b}
28
+ end
17
29
  end
18
30
 
19
31
  let(:agent) { double('agent', config: config) }
@@ -117,5 +129,79 @@ describe Mamiya::Agent::Tasks::Clean do
117
129
  )
118
130
  end
119
131
  end
132
+
133
+ describe "releases_dir" do
134
+ before do
135
+ # TODO: XXX: this may remove ongoing preparing somewhat
136
+ deploy_to_a.join('releases').tap do |path|
137
+ path.mkdir
138
+ path.join('1').mkdir
139
+ path.join('2').mkdir
140
+ path.join('3').mkdir
141
+ end
142
+
143
+ deploy_to_b.join('releases').tap do |path|
144
+ path.mkdir
145
+ path.join('1').mkdir
146
+ path.join('2').mkdir
147
+ end
148
+ end
149
+
150
+ it "cleans up" do
151
+ expect(agent).to receive(:trigger).with('release', action: 'remove', app: :app_a, pkg: '1', coalesce: false)
152
+
153
+ task.execute
154
+
155
+ existences = Hash[
156
+ [
157
+ deploy_to_a.join('releases', '1'),
158
+ deploy_to_a.join('releases', '2'),
159
+ deploy_to_a.join('releases', '3'),
160
+ deploy_to_b.join('releases', '1'),
161
+ deploy_to_b.join('releases', '2'),
162
+ ].map { |file|
163
+ [file, file.exist?]
164
+ }
165
+ ]
166
+
167
+ expect(existences).to eq(
168
+ deploy_to_a.join('releases', '1') => false,
169
+ deploy_to_a.join('releases', '2') => true,
170
+ deploy_to_a.join('releases', '3') => true,
171
+ deploy_to_b.join('releases', '1') => true,
172
+ deploy_to_b.join('releases', '2') => true,
173
+ )
174
+ end
175
+
176
+ context "with current release" do
177
+ before do
178
+ deploy_to_a.join('current').make_symlink deploy_to_a.join('releases', '1')
179
+ end
180
+
181
+ it "cleans up" do
182
+ task.execute
183
+
184
+ existences = Hash[
185
+ [
186
+ deploy_to_a.join('releases', '1'),
187
+ deploy_to_a.join('releases', '2'),
188
+ deploy_to_a.join('releases', '3'),
189
+ deploy_to_b.join('releases', '1'),
190
+ deploy_to_b.join('releases', '2'),
191
+ ].map { |file|
192
+ [file, file.exist?]
193
+ }
194
+ ]
195
+
196
+ expect(existences).to eq(
197
+ deploy_to_a.join('releases', '1') => true,
198
+ deploy_to_a.join('releases', '2') => true,
199
+ deploy_to_a.join('releases', '3') => true,
200
+ deploy_to_b.join('releases', '1') => true,
201
+ deploy_to_b.join('releases', '2') => true,
202
+ )
203
+ end
204
+ end
205
+ end
120
206
  end
121
207
  end