marathon-api 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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