marathon-api 0.9.0

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 (64) hide show
  1. checksums.yaml +7 -0
  2. data/.cane +3 -0
  3. data/.gitignore +7 -0
  4. data/.simplecov +5 -0
  5. data/.travis.yml +10 -0
  6. data/Gemfile +3 -0
  7. data/LICENSE +22 -0
  8. data/README.md +91 -0
  9. data/Rakefile +34 -0
  10. data/TESTING.md +49 -0
  11. data/fixtures/marathon_docker_sample.json +14 -0
  12. data/fixtures/marathon_docker_sample_2.json +14 -0
  13. data/fixtures/vcr/Marathon/_info/returns_the_info_hash.yml +30 -0
  14. data/fixtures/vcr/Marathon/_ping/ping/.yml +35 -0
  15. data/fixtures/vcr/Marathon_App/_changes/changes_the_app.yml +57 -0
  16. data/fixtures/vcr/Marathon_App/_changes/fails_with_stange_attributes.yml +32 -0
  17. data/fixtures/vcr/Marathon_App/_delete/deletes_the_app.yml +30 -0
  18. data/fixtures/vcr/Marathon_App/_delete/fails_deleting_not_existing_app.yml +30 -0
  19. data/fixtures/vcr/Marathon_App/_get/fails_getting_not_existing_app.yml +30 -0
  20. data/fixtures/vcr/Marathon_App/_get/gets_the_app.yml +30 -0
  21. data/fixtures/vcr/Marathon_App/_list/lists_apps.yml +32 -0
  22. data/fixtures/vcr/Marathon_App/_restart/fails_restarting_not_existing_app.yml +30 -0
  23. data/fixtures/vcr/Marathon_App/_start/fails_getting_not_existing_app.yml +30 -0
  24. data/fixtures/vcr/Marathon_App/_start/starts_the_app.yml +32 -0
  25. data/fixtures/vcr/Marathon_App/_tasks/has_tasks.yml +30 -0
  26. data/fixtures/vcr/Marathon_App/_version/gets_a_version.yml +61 -0
  27. data/fixtures/vcr/Marathon_App/_versions/gets_versions.yml +32 -0
  28. data/fixtures/vcr/Marathon_Deployment/_delete/deletes_deployments.yml +61 -0
  29. data/fixtures/vcr/Marathon_Deployment/_list/lists_deployments.yml +90 -0
  30. data/fixtures/vcr/Marathon_EventSubscriptions/_list/lists_callbacks.yml +30 -0
  31. data/fixtures/vcr/Marathon_EventSubscriptions/_register/registers_callback.yml +30 -0
  32. data/fixtures/vcr/Marathon_EventSubscriptions/_unregister/unregisters_callback.yml +30 -0
  33. data/fixtures/vcr/Marathon_Leader/_delete/delete/.yml +30 -0
  34. data/fixtures/vcr/Marathon_Leader/_get/get/.yml +30 -0
  35. data/fixtures/vcr/Marathon_Queue/_list/lists_queue.yml +33 -0
  36. data/fixtures/vcr/Marathon_Task/_delete/kills_a_tasks_of_an_app.yml +57 -0
  37. data/fixtures/vcr/Marathon_Task/_delete_all/kills_all_tasks_of_an_app.yml +30 -0
  38. data/fixtures/vcr/Marathon_Task/_get/gets_tasks_of_an_app.yml +30 -0
  39. data/fixtures/vcr/Marathon_Task/_list/lists_running_tasks.yml +30 -0
  40. data/fixtures/vcr/Marathon_Task/_list/lists_tasks.yml +30 -0
  41. data/lib/marathon.rb +65 -0
  42. data/lib/marathon/app.rb +200 -0
  43. data/lib/marathon/connection.rb +97 -0
  44. data/lib/marathon/deployment.rb +60 -0
  45. data/lib/marathon/error.rb +62 -0
  46. data/lib/marathon/event_subscriptions.rb +33 -0
  47. data/lib/marathon/leader.rb +19 -0
  48. data/lib/marathon/queue.rb +36 -0
  49. data/lib/marathon/task.rb +85 -0
  50. data/lib/marathon/util.rb +35 -0
  51. data/lib/marathon/version.rb +3 -0
  52. data/marathon-api.gemspec +31 -0
  53. data/spec/marathon/app_spec.rb +334 -0
  54. data/spec/marathon/connection_spec.rb +40 -0
  55. data/spec/marathon/deployment_spec.rb +95 -0
  56. data/spec/marathon/error_spec.rb +40 -0
  57. data/spec/marathon/event_subscriptions_spec.rb +37 -0
  58. data/spec/marathon/leader_spec.rb +21 -0
  59. data/spec/marathon/marathon_spec.rb +47 -0
  60. data/spec/marathon/queue_spec.rb +62 -0
  61. data/spec/marathon/task_spec.rb +100 -0
  62. data/spec/marathon/util_spec.rb +44 -0
  63. data/spec/spec_helper.rb +34 -0
  64. metadata +271 -0
@@ -0,0 +1,334 @@
1
+ require 'spec_helper'
2
+
3
+ describe Marathon::App do
4
+
5
+ describe '#to_s' do
6
+ subject { described_class.new({ 'id' => '/app/foo' }) }
7
+
8
+ let(:expected_string) do
9
+ "Marathon::App { :id => /app/foo }"
10
+ end
11
+
12
+ its(:to_s) { should == expected_string }
13
+ end
14
+
15
+ describe '#to_json' do
16
+ subject { described_class.new({ 'id' => '/app/foo' }) }
17
+
18
+ let(:expected_string) do
19
+ '{"id":"/app/foo"}'
20
+ end
21
+
22
+ its(:to_json) { should == expected_string }
23
+ end
24
+
25
+ describe '#check_read_only' do
26
+ subject { described_class.new({ 'id' => '/ubuntu2' }, true) }
27
+
28
+ it 'does not allow changing the app' do
29
+ expect { subject.change!({}) }.to raise_error(Marathon::Error::ArgumentError)
30
+ end
31
+ end
32
+
33
+ describe '#tasks' do
34
+ subject { described_class.new({ 'id' => '/ubuntu2' }) }
35
+
36
+ it 'checks for read only' do
37
+ expect(subject).to receive(:check_read_only)
38
+ subject.info['tasks'] = []
39
+ subject.tasks
40
+ end
41
+
42
+ it 'has tasks', :vcr do
43
+ tasks = subject.tasks
44
+ expect(tasks).to be_instance_of(Array)
45
+ expect(tasks.size).to eq(1)
46
+ expect(tasks.first).to be_instance_of(Marathon::Task)
47
+ expect(tasks.first.appId).to eq(subject.id)
48
+ end
49
+
50
+ it 'loads tasks from API when not loaded already' do
51
+ subject.info['tasks'] = nil
52
+ expect(subject).to receive(:refresh) { subject.info['tasks'] = [] }
53
+ expect(subject.tasks).to eq([])
54
+ end
55
+
56
+ it 'shows already loaded tasks w/o API call' do
57
+ subject.info['tasks'] = []
58
+ expect(subject).not_to receive(:refresh)
59
+ expect(subject.tasks).to eq([])
60
+ end
61
+ end
62
+
63
+ describe '#versions' do
64
+ subject { described_class.new({ 'id' => '/ubuntu2' }) }
65
+
66
+ it 'loads versions from API' do
67
+ expect(described_class).to receive(:versions).with('/ubuntu2') { ['foo-version'] }
68
+ expect(subject.versions).to eq(['foo-version'])
69
+ end
70
+
71
+ it 'loads version from API' do
72
+ expect(described_class).to receive(:version).with('/ubuntu2', 'foo-version') {
73
+ Marathon::App.new({'id' => '/ubuntu2', 'version' => 'foo-version'}, true)
74
+ }
75
+ expect(subject.versions('foo-version').version).to eq('foo-version')
76
+ end
77
+ end
78
+
79
+ describe '#start!' do
80
+ let(:app) { described_class.new({ 'id' => '/app/foo' }) }
81
+
82
+ it 'checks for read only' do
83
+ expect(subject).to receive(:check_read_only)
84
+ expect(described_class).to receive(:start) { described_class.new }
85
+ subject.start!
86
+ end
87
+
88
+ it 'starts the app' do
89
+ expect(described_class).to receive(:start).with({ 'id' => '/app/foo'}) do
90
+ described_class.new({ 'id' => '/app/foo', 'started' => true })
91
+ end
92
+ app.start!
93
+ expect(app.info['started']).to be(true)
94
+ end
95
+ end
96
+
97
+ describe '#refresh' do
98
+ let(:app) { described_class.new({ 'id' => '/app/foo' }) }
99
+
100
+ it 'checks for read only' do
101
+ expect(subject).to receive(:check_read_only)
102
+ expect(described_class).to receive(:get) { described_class.new }
103
+ subject.refresh
104
+ end
105
+
106
+ it 'refreshs the app' do
107
+ expect(described_class).to receive(:get).with('/app/foo') do
108
+ described_class.new({ 'id' => '/app/foo', 'refreshed' => true })
109
+ end
110
+ app.refresh
111
+ expect(app.info['refreshed']).to be(true)
112
+ end
113
+ end
114
+
115
+ describe '#restart!' do
116
+ let(:app) { described_class.new({ 'id' => '/app/foo' }) }
117
+
118
+ it 'checks for read only' do
119
+ expect(subject).to receive(:check_read_only)
120
+ expect(described_class).to receive(:restart)
121
+ subject.restart!
122
+ end
123
+
124
+ it 'restarts the app' do
125
+ expect(described_class).to receive(:restart)
126
+ .with('/app/foo', false)
127
+ app.restart!
128
+ end
129
+
130
+ it 'restarts the app, force' do
131
+ expect(described_class).to receive(:restart)
132
+ .with('/app/foo', true)
133
+ app.restart!(true)
134
+ end
135
+ end
136
+
137
+ describe '#change!' do
138
+ let(:app) { described_class.new({ 'id' => '/app/foo' }) }
139
+
140
+ it 'checks for read only' do
141
+ expect(subject).to receive(:check_read_only)
142
+ expect(described_class).to receive(:change)
143
+ subject.change!({})
144
+ end
145
+
146
+ it 'changes the app' do
147
+ expect(described_class).to receive(:change).with('/app/foo', {'instances' => 9000 }, false)
148
+ app.change!('instances' => 9000)
149
+ end
150
+ end
151
+
152
+ describe '#roll_back!' do
153
+ let(:app) { described_class.new({ 'id' => '/app/foo', 'instances' => 10 }) }
154
+
155
+ it 'checks for read only' do
156
+ expect(subject).to receive(:check_read_only)
157
+ expect(described_class).to receive(:change)
158
+ subject.roll_back!('old_version')
159
+ end
160
+
161
+ it 'changes the app' do
162
+ expect(app).to receive(:change!).with({'version' => 'old_version' }, false)
163
+ app.roll_back!('old_version')
164
+ end
165
+
166
+ it 'changes the app with force' do
167
+ expect(app).to receive(:change!).with({'version' => 'old_version' }, true)
168
+ app.roll_back!('old_version', true)
169
+ end
170
+ end
171
+
172
+ describe '#scale!' do
173
+ let(:app) { described_class.new({ 'id' => '/app/foo', 'instances' => 10 }) }
174
+
175
+ it 'checks for read only' do
176
+ expect(subject).to receive(:check_read_only)
177
+ expect(described_class).to receive(:change)
178
+ subject.scale!(5)
179
+ end
180
+
181
+ it 'changes the app' do
182
+ expect(app).to receive(:change!).with({'instances' => 9000 }, false)
183
+ app.scale!(9000)
184
+ end
185
+
186
+ it 'changes the app with force' do
187
+ expect(app).to receive(:change!).with({'instances' => 9000 }, true)
188
+ app.scale!(9000, true)
189
+ end
190
+ end
191
+
192
+ describe '#suspend!' do
193
+ let(:app) { described_class.new({ 'id' => '/app/foo', 'instances' => 10 }) }
194
+
195
+ it 'checks for read only' do
196
+ expect(subject).to receive(:check_read_only)
197
+ expect(described_class).to receive(:change)
198
+ subject.suspend!
199
+ end
200
+
201
+ it 'scales the app to 0' do
202
+ expect(app).to receive(:scale!).with(0, false)
203
+ app.suspend!
204
+ end
205
+
206
+ it 'scales the app to 0 with force' do
207
+ expect(app).to receive(:scale!).with(0, true)
208
+ app.suspend!(true)
209
+ end
210
+ end
211
+
212
+ describe '.list' do
213
+ subject { described_class }
214
+
215
+ it 'passes arguments to api call' do
216
+ expect(Marathon.connection).to receive(:get)
217
+ .with('/v2/apps', {:cmd => 'foo', :embed => 'apps.tasks'})
218
+ .and_return({ 'apps' => [] })
219
+ described_class.list('foo', 'apps.tasks')
220
+ end
221
+
222
+ it 'raises error when run with strange embed' do
223
+ expect {
224
+ described_class.list(nil, 'foo')
225
+ }.to raise_error(Marathon::Error::ArgumentError)
226
+ end
227
+
228
+ it 'lists apps', :vcr do
229
+ apps = described_class.list
230
+ expect(apps.size).not_to eq(0)
231
+ expect(apps.first).to be_instance_of(described_class)
232
+ expect(apps.first.cpus).to eq(0.1)
233
+ end
234
+
235
+ end
236
+
237
+ describe '.start' do
238
+ subject { described_class }
239
+
240
+ it 'starts the app', :vcr do
241
+ app = described_class.start({ :id => '/test', :cmd => 'sleep 10', :instances => 1, :cpus => 0.1, :mem => 32})
242
+ expect(app).to be_instance_of(described_class)
243
+ expect(app.id).to eq('/test')
244
+ expect(app.instances).to eq(1)
245
+ expect(app.cpus).to eq(0.1)
246
+ expect(app.mem).to eq(32)
247
+ end
248
+
249
+ it 'fails getting not existing app', :vcr do
250
+ expect {
251
+ described_class.get('fooo app')
252
+ }.to raise_error(Marathon::Error::NotFoundError)
253
+ end
254
+ end
255
+
256
+ describe '.get' do
257
+ subject { described_class }
258
+
259
+ it 'gets the app', :vcr do
260
+ app = described_class.get('/ubuntu')
261
+ expect(app).to be_instance_of(described_class)
262
+ expect(app.id).to eq('/ubuntu')
263
+ expect(app.instances).to eq(1)
264
+ expect(app.cpus).to eq(0.1)
265
+ expect(app.mem).to eq(64)
266
+ end
267
+
268
+ it 'fails getting not existing app', :vcr do
269
+ expect {
270
+ described_class.get('fooo app')
271
+ }.to raise_error(Marathon::Error::NotFoundError)
272
+ end
273
+ end
274
+
275
+ describe '.delete' do
276
+ subject { described_class }
277
+
278
+ it 'deletes the app', :vcr do
279
+ described_class.delete('/ubuntu')
280
+ end
281
+
282
+ it 'fails deleting not existing app', :vcr do
283
+ expect {
284
+ described_class.delete('fooo app')
285
+ }.to raise_error(Marathon::Error::NotFoundError)
286
+ end
287
+ end
288
+
289
+ describe '.restart' do
290
+ subject { described_class }
291
+
292
+ it 'fails restarting not existing app', :vcr do
293
+ expect {
294
+ described_class.restart('fooo app')
295
+ }.to raise_error(Marathon::Error::NotFoundError)
296
+ end
297
+ end
298
+
299
+ describe '.changes' do
300
+ subject { described_class }
301
+
302
+ it 'changes the app', :vcr do
303
+ described_class.change('/ubuntu2', { 'instances' => 2 })
304
+ described_class.change('/ubuntu2', { 'instances' => 1 }, true)
305
+ end
306
+
307
+ it 'fails with stange attributes', :vcr do
308
+ expect {
309
+ described_class.change('/ubuntu2', { 'instances' => 'foo' })
310
+ }.to raise_error(Marathon::Error::ClientError)
311
+ end
312
+ end
313
+
314
+ describe '.versions' do
315
+ subject { described_class }
316
+
317
+ it 'gets versions', :vcr do
318
+ versions = subject.versions('/ubuntu2')
319
+ expect(versions).to be_instance_of(Array)
320
+ expect(versions.first).to be_instance_of(String)
321
+ end
322
+ end
323
+
324
+ describe '.version' do
325
+ subject { described_class }
326
+
327
+ it 'gets a version', :vcr do
328
+ versions = subject.versions('/ubuntu2')
329
+ version = subject.version('/ubuntu2', versions.first)
330
+ expect(version).to be_instance_of(Marathon::App)
331
+ expect(version.read_only).to be(true)
332
+ end
333
+ end
334
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe Marathon::Connection do
4
+
5
+ describe '#to_s' do
6
+ subject { described_class.new('http://foo:8080') }
7
+
8
+ let(:expected_string) do
9
+ "Marathon::Connection { :url => http://foo:8080 }"
10
+ end
11
+
12
+ its(:to_s) { should == expected_string }
13
+ end
14
+
15
+ describe '#request' do
16
+ subject { described_class.new('http://foo.example.org:8080') }
17
+
18
+ it 'raises IOError on SocketError' do
19
+ allow(described_class).to receive(:send) { raise SocketError.new }
20
+ expect {
21
+ subject.get('/v2/some/api/path')
22
+ }.to raise_error(Marathon::Error::IOError)
23
+ end
24
+
25
+ it 'raises IOError on Errno' do
26
+ allow(described_class).to receive(:send) { raise Errno::EINTR.new }
27
+ expect {
28
+ subject.get('/v2/some/api/path')
29
+ }.to raise_error(Marathon::Error::IOError)
30
+ end
31
+
32
+ it 'raises original error when unknown' do
33
+ allow(described_class).to receive(:send) { raise RuntimeError.new }
34
+ expect {
35
+ subject.get('/v2/some/api/path')
36
+ }.to raise_error(RuntimeError)
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,95 @@
1
+ require 'spec_helper'
2
+
3
+ EXAMPLE = {
4
+ "affectedApps" => ["/test"],
5
+ "id" => "867ed450-f6a8-4d33-9b0e-e11c5513990b",
6
+ "steps" => [
7
+ [
8
+ {
9
+ "action" => "ScaleApplication",
10
+ "app" => "/test"
11
+ }
12
+ ]
13
+ ],
14
+ "currentActions" => [
15
+ {
16
+ "action" => "ScaleApplication",
17
+ "app" => "/test"
18
+ }
19
+ ],
20
+ "version" => "2014-08-26T08:18:03.595Z",
21
+ "currentStep" => 1,
22
+ "totalSteps" => 1
23
+ }
24
+
25
+ describe Marathon::Deployment do
26
+
27
+ describe '#to_s' do
28
+ subject { described_class.new(EXAMPLE) }
29
+
30
+ let(:expected_string) do
31
+ 'Marathon::Deployment { ' \
32
+ + ':id => 867ed450-f6a8-4d33-9b0e-e11c5513990b :affectedApps => ["/test"] :currentStep => 1 :totalSteps => 1 }'
33
+ end
34
+
35
+ its(:to_s) { should == expected_string }
36
+ end
37
+
38
+ describe '#to_json' do
39
+ subject { described_class.new(EXAMPLE) }
40
+
41
+ its(:to_json) { should == EXAMPLE.to_json }
42
+ end
43
+
44
+ describe 'attributes' do
45
+ subject { described_class.new(EXAMPLE) }
46
+
47
+ its(:id) { should == EXAMPLE['id'] }
48
+ its(:affectedApps) { should == EXAMPLE['affectedApps'] }
49
+ its(:version) { should == EXAMPLE['version'] }
50
+ its(:currentStep) { should == EXAMPLE['currentStep'] }
51
+ its(:totalSteps) { should == EXAMPLE['totalSteps'] }
52
+ end
53
+
54
+ describe '#delete' do
55
+ subject { described_class.new(EXAMPLE) }
56
+
57
+ it 'deletes the deployment' do
58
+ expect(described_class).to receive(:delete).with(EXAMPLE['id'], false)
59
+ subject.delete
60
+ end
61
+
62
+ it 'force deletes the deployment' do
63
+ expect(described_class).to receive(:delete).with(EXAMPLE['id'], true)
64
+ subject.delete(true)
65
+ end
66
+ end
67
+
68
+ describe '.list' do
69
+ subject { described_class }
70
+
71
+ it 'lists deployments', :vcr do
72
+ # start a deployment
73
+ Marathon::App.change('/test', {'instances' => 0})
74
+ sleep 1
75
+ Marathon::App.change('/test', {'instances' => 2})
76
+ sleep 1
77
+
78
+ deployments = subject.list
79
+ expect(deployments).to be_instance_of(Array)
80
+ expect(deployments.first).to be_instance_of(Marathon::Deployment)
81
+ end
82
+ end
83
+
84
+ describe '.delete' do
85
+ subject { described_class }
86
+
87
+ it 'deletes deployments', :vcr do
88
+ # start a deployment
89
+ json = Marathon::App.change('/test', {'instances' => 1})
90
+ id = json['deploymentId']
91
+ subject.delete(id)
92
+ end
93
+ end
94
+
95
+ end