mamiya 0.0.1.alpha24 → 0.0.1.beta1

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