right_infrastructure_agent 1.1.2

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.
@@ -0,0 +1,546 @@
1
+ # Copyright (c) 2009-2011 RightScale, Inc, All Rights Reserved Worldwide.
2
+ #
3
+ # THIS PROGRAM IS CONFIDENTIAL AND PROPRIETARY TO RIGHTSCALE
4
+ # AND CONSTITUTES A VALUABLE TRADE SECRET. Any unauthorized use,
5
+ # reproduction, modification, or disclosure of this program is
6
+ # strictly prohibited. Any use of this program by an authorized
7
+ # licensee is strictly subject to the terms and conditions,
8
+ # including confidentiality obligations, set forth in the applicable
9
+ # License Agreement between RightScale.com, Inc. and
10
+ # the licensee.
11
+
12
+ require File.join(File.dirname(__FILE__), 'spec_helper')
13
+
14
+ describe RightScale::ModelsHelper do
15
+
16
+ include RightScale::ModelsHelper
17
+
18
+ class Account; end
19
+ class AuditEntry; end
20
+ class InstanceApiToken; end
21
+ class Permission; end
22
+ class Repository; end
23
+ class RightScript; end
24
+ class Setting; end
25
+ class ServerTemplateChefRecipe; end
26
+ class User; end
27
+ class UserCredential; end
28
+
29
+ before(:each) do
30
+ flexmock(RightScale::Log).should_receive(:info).never.by_default
31
+ flexmock(RightScale::Log).should_receive(:warning).never.by_default
32
+ flexmock(RightScale::Log).should_receive(:error).never.by_default
33
+ @audit_formatter = flexmock(RightScale::AuditFormatter)
34
+ @audit_formatter.should_receive(:error).never.by_default
35
+ @audit = flexmock("audit")
36
+ @audit.should_receive(:append).never.by_default
37
+ @last_error = nil
38
+ end
39
+
40
+ context :query do
41
+ it 'should run query, yielding to block and returning result from query block' do
42
+ called = 0
43
+ result = query("query database") { called += 1 }
44
+ called.should == 1
45
+ result.should == 1
46
+ end
47
+
48
+ it 'should catch and log exception and return nil by default' do
49
+ flexmock(RightScale::Log).should_receive(:error).with("Failed to query database", NoMethodError, :trace).once
50
+ called = 0
51
+ result = query("query database") do
52
+ called += 1
53
+ nil + "string"
54
+ end
55
+ called.should == 1
56
+ result.should == nil
57
+ error = /Failed to query database \(NoMethodError.*undefined method.*models_helper_spec.*\)$/
58
+ (@last_error.should =~ error).should be_true
59
+ end
60
+
61
+ it 'should audit exception if audit enabled' do
62
+ error = /Failed to query database \(NoMethodError.*undefined method.*models_helper_spec.*\)$/
63
+ @audit_formatter.should_receive(:error).with(error).once
64
+ @audit.should_receive(:append).once
65
+ flexmock(RightScale::Log).should_receive(:error).once
66
+ called = 0
67
+ result = query("query database", :audit => @audit) do
68
+ called += 1
69
+ nil + "string"
70
+ end
71
+ called.should == 1
72
+ result.should == nil
73
+ (@last_error.should =~ error).should be_true
74
+ end
75
+
76
+ it 'should return nil if catch RecordNotFound exception and not log error' do
77
+ called = 0
78
+ result = query("query database") do
79
+ called += 1
80
+ raise ActiveRecord::RecordNotFound.new("Missing")
81
+ end
82
+ called.should == 1
83
+ result.should == nil
84
+ @last_error.should be_nil
85
+ end
86
+
87
+ it 'should raise RetryableError if exceed retries, :retryable_error enabled, and error retryable' do
88
+ flexmock(RightScale::Log).should_receive(:info).with("Retrying query...").times(3)
89
+ flexmock(RightScale::Log).should_receive(:warning).with("Aborting query after 3 failed retries").once
90
+ flexmock(RightScale::Log).should_receive(:error).with("Failed running MySQL query",
91
+ ActiveRecord::StatementInvalid, :trace).times(3)
92
+ flexmock(RightScale::Log).should_receive(:error).with("Failed to query database but retryable",
93
+ RightScale::Exceptions::RetryableError, :trace).once
94
+ lambda do
95
+ query("query database", :retryable_error => true) do
96
+ raise ActiveRecord::StatementInvalid.new("Deadlock found")
97
+ end
98
+ end.should raise_error(RightScale::Exceptions::RetryableError)
99
+ error = /Failed to query database but retryable \(RightScale::Exceptions::RetryableError: RightScale database temporarily unavailable in .*models_helper.*\)$/
100
+ (@last_error.should =~ error).should be_true
101
+ end
102
+
103
+ it 'should not raise RetryableError if exceed retries, :retryable_error enabled, but error not retryable' do
104
+ flexmock(RightScale::Log).should_receive(:info).with("Retrying query...").times(3)
105
+ flexmock(RightScale::Log).should_receive(:warning).with("Aborting query after 3 failed retries").once
106
+ flexmock(RightScale::Log).should_receive(:error).with("Failed running MySQL query",
107
+ ActiveRecord::StatementInvalid, :trace).times(3)
108
+ flexmock(RightScale::Log).should_receive(:error).with("Failed to query database",
109
+ ActiveRecord::StatementInvalid, :trace).once
110
+ query("query database", :retryable_error => true) do
111
+ raise ActiveRecord::StatementInvalid.new("Invalid query")
112
+ end
113
+ error = /Failed to query database \(ActiveRecord::StatementInvalid.*Invalid query in .*models_helper_spec.*\)$/
114
+ (@last_error.should =~ error).should be_true
115
+ end
116
+ end
117
+
118
+ context :run_query do
119
+ it 'should return result of executed block' do
120
+ run_query { "result" }.should == "result"
121
+ end
122
+
123
+ it 'should treat RecordNotFound as nil result and not raise error' do
124
+ run_query { raise ActiveRecord::RecordNotFound }.should be_nil
125
+ end
126
+
127
+ it 'should retry if exception is retryable' do
128
+ flexmock(RightScale::Log).should_receive(:info).with("Retrying query...").once
129
+ flexmock(RightScale::Log).should_receive(:error).with("Failed running MySQL query",
130
+ ActiveRecord::StatementInvalid, :trace).once
131
+ called = 0
132
+ result = run_query do
133
+ called += 1
134
+ raise ActiveRecord::StatementInvalid.new("Invalid query") if called == 1
135
+ "result"
136
+ end
137
+ called.should == 2
138
+ result.should == "result"
139
+ end
140
+
141
+ it 'should raise exception if not retryable' do
142
+ flexmock(RightScale::Log).should_receive(:info).with("Retrying query...").never
143
+ lambda do
144
+ run_query do
145
+ raise ArgumentError
146
+ end
147
+ end.should raise_error(ArgumentError)
148
+ end
149
+
150
+ it 'should retry at most 3 times' do
151
+ flexmock(RightScale::Log).should_receive(:info).with("Retrying query...").times(3)
152
+ flexmock(RightScale::Log).should_receive(:warning).with("Aborting query after 3 failed retries").once
153
+ flexmock(RightScale::Log).should_receive(:error).with("Failed running MySQL query",
154
+ ActiveRecord::StatementInvalid, :trace).times(3)
155
+ called = 0
156
+ lambda do
157
+ run_query do
158
+ called += 1
159
+ raise ActiveRecord::StatementInvalid.new("Invalid query")
160
+ end
161
+ end.should raise_error(ActiveRecord::StatementInvalid)
162
+ called.should == 4
163
+ end
164
+
165
+ it 'should raise RetryableError if exceed retries, :retryable_error enabled, and error retryable' do
166
+ flexmock(RightScale::Log).should_receive(:info).with("Retrying query...").times(3)
167
+ flexmock(RightScale::Log).should_receive(:warning).with("Aborting query after 3 failed retries").once
168
+ flexmock(RightScale::Log).should_receive(:error).with("Failed running MySQL query",
169
+ ActiveRecord::StatementInvalid, :trace).times(3)
170
+ called = 0
171
+ lambda do
172
+ run_query(:retryable_error => true) do
173
+ called += 1
174
+ raise ActiveRecord::StatementInvalid.new("Deadlock found")
175
+ end
176
+ end.should raise_error(RightScale::Exceptions::RetryableError, RightScale::ModelsHelper::DEFAULT_RETRY_MESSAGE)
177
+ called.should == 4
178
+ end
179
+
180
+ it 'should raise RetryableError with account unavailable message if wrong shard error' do
181
+ flexmock(RightScale::Log).should_receive(:info).with("Retrying query...").times(3)
182
+ flexmock(RightScale::Log).should_receive(:warning).with("Aborting query after 3 failed retries").once
183
+ flexmock(RightScale::Log).should_receive(:error).with("Failed running MySQL query",
184
+ ActiveRecord::StatementInvalid, :trace).times(3)
185
+ called = 0
186
+ lambda do
187
+ run_query(:retryable_error => true) do
188
+ called += 1
189
+ raise ActiveRecord::StatementInvalid.new(RightScale::ModelsHelper::WRONG_SHARD_ERROR)
190
+ end
191
+ end.should raise_error(RightScale::Exceptions::RetryableError, RightScale::ModelsHelper::WRONG_SHARD_RETRY_MESSAGE)
192
+ called.should == 4
193
+ end
194
+
195
+ it 'should not raise RetryableError if exceed retries, :retryable_error enabled, but error not retryable' do
196
+ flexmock(RightScale::Log).should_receive(:info).with("Retrying query...").times(3)
197
+ flexmock(RightScale::Log).should_receive(:warning).with("Aborting query after 3 failed retries").once
198
+ flexmock(RightScale::Log).should_receive(:error).with("Failed running MySQL query",
199
+ ActiveRecord::StatementInvalid, :trace).times(3)
200
+ called = 0
201
+ lambda do
202
+ run_query(:retryable_error => true) do
203
+ called += 1
204
+ raise ActiveRecord::StatementInvalid.new("Invalid query")
205
+ end
206
+ end.should raise_error(ActiveRecord::StatementInvalid)
207
+ called.should == 4
208
+ end
209
+ end
210
+
211
+ context :is_retryable_error? do
212
+ it 'should be permissive of any MysqlError or ActiveRecord::ActiveRecordError for local test' do
213
+ is_retryable_error?(ArgumentError.new("anything"), local = true).should be_false
214
+ is_retryable_error?(MysqlError.new("anything"), local = true).should be_true
215
+ is_retryable_error?(MysqlError.new("has gone away"), local = true).should be_true
216
+ is_retryable_error?(ActiveRecord::StatementInvalid.new("anything"), local = true).should be_true
217
+ is_retryable_error?(ActiveRecord::StatementInvalid.new("Deadlock found"), local = true).should be_true
218
+ end
219
+
220
+ it 'should use selected ActiveRecord::ActiveRecordError retryable errors to restrict for external test' do
221
+ is_retryable_error?(ArgumentError.new("anything")).should be_false
222
+ is_retryable_error?(MysqlError.new("anything")).should be_false
223
+ is_retryable_error?(MysqlError.new("has gone away")).should be_true
224
+ is_retryable_error?(ActiveRecord::StatementInvalid.new("anything")).should be_false
225
+ is_retryable_error?(ActiveRecord::StatementInvalid.new("Deadlock found")).should be_true
226
+ end
227
+ end
228
+
229
+ context :retrieve_or_create_audit do
230
+ it 'should retrieve audit when id specified' do
231
+ flexmock(self).should_receive(:audit_entry).with(123, :audit_id => 123).and_return("audit")
232
+ flexmock(AuditEntry).should_receive(:create!).never
233
+ retrieve_or_create_audit("instance", "summary", "detail", "account", "user", :audit_id => 123).should == "audit"
234
+ end
235
+
236
+ it 'should create audit when agent identity specified instead of id' do
237
+ flexmock(self).should_receive(:audit_entry).with(1, :audit_id => 123).never
238
+ flexmock(AuditEntry).should_receive(:create!).with({:auditee => "instance", :summary => "summary", :detail => "detail",
239
+ :account => "account", :user => "user"}).and_return("audit").once
240
+ retrieve_or_create_audit("instance", "summary", "detail", "account", "user", :agent_identity => "111").should == "audit"
241
+
242
+ end
243
+
244
+ it 'should raise exception if neiher audit id or agent identity specified' do
245
+ lambda do
246
+ retrieve_or_create_audit("instance", "summary", "detail", "account", "user")
247
+ end.should raise_error(ArgumentError)
248
+ end
249
+ end
250
+
251
+ context :retrieve_or_default_user do
252
+ before(:each) do
253
+ @user = flexmock("user", :id= => 0, :id => 123)
254
+ @account = flexmock("account", :users => [@user])
255
+ @instance = flexmock("instance", :account => @account)
256
+ end
257
+
258
+ it 'should return default user if not user id supplied' do
259
+ flexmock(User).should_receive(:new).with(:email => "alerter@rightscale.com").and_return(@user).once
260
+ retrieve_or_default_user(@instance).should == @user
261
+ end
262
+
263
+ it 'should retrieve user if user id supplied' do
264
+ flexmock(User).should_receive(:new).never
265
+ result = retrieve_or_default_user(@instance, :user_id => 123).should == @user
266
+ end
267
+
268
+ it 'should return error result if no user found' do
269
+ retrieve_or_default_user(@instance, :user_id => 124).should be_nil
270
+ end
271
+ end
272
+
273
+ context :retrieve do
274
+ it 'should yield and return result from block' do
275
+ called = 0
276
+ result = retrieve("something") { called += 1 }
277
+ called.should == 1
278
+ result.should == 1
279
+ @last_error.should be_nil
280
+ end
281
+
282
+ it 'should detect not found but not log warning nor audit by default' do
283
+ called = 0
284
+ result = retrieve("something") { called += 1; nil }
285
+ called.should == 1
286
+ result.should == nil
287
+ @last_error.should == "Could not find something"
288
+ end
289
+
290
+ it 'should detect not found and log warning if log enabled' do
291
+ flexmock(RightScale::Log).should_receive(:warning).with(/Could not find something/).once
292
+ called = 0
293
+ result = retrieve("something", :log => true) { called += 1; nil }
294
+ called.should == 1
295
+ result.should == nil
296
+ @last_error.should == "Could not find something"
297
+ end
298
+
299
+ it 'should detect not found and audit if audit enabled' do
300
+ @audit_formatter.should_receive(:error).with("Could not find something").once
301
+ @audit.should_receive(:append).once
302
+ called = 0
303
+ result = retrieve("something", :audit => @audit) { called += 1; nil }
304
+ called.should == 1
305
+ result.should == nil
306
+ @last_error.should == "Could not find something"
307
+ end
308
+
309
+ it 'should catch and log exception and return nil by default' do
310
+ flexmock(RightScale::Log).should_receive(:error).with("Failed to retrieve something", NoMethodError, :trace).once
311
+ called = 0
312
+ result = retrieve("something") do
313
+ called += 1
314
+ nil + "string"
315
+ end
316
+ called.should == 1
317
+ result.should == nil
318
+ error = /Failed to retrieve something \(NoMethodError.*undefined method.*models_helper_spec.*\)$/
319
+ (@last_error.should =~ error).should be_true
320
+ end
321
+
322
+ it 'should audit exception if audit enabled' do
323
+ error = /Failed to retrieve something \(NoMethodError.*undefined method.*models_helper_spec.*\)$/
324
+ @audit_formatter.should_receive(:error).with(error).once
325
+ @audit.should_receive(:append).once
326
+ flexmock(RightScale::Log).should_receive(:error).once
327
+ called = 0
328
+ result = retrieve("something", :audit => @audit) do
329
+ called += 1
330
+ nil + "string"
331
+ end
332
+ called.should == 1
333
+ result.should == nil
334
+ (@last_error.should =~ error).should be_true
335
+ end
336
+
337
+ it 'should not log or audit an error from a previous retrieval' do
338
+ called = 0
339
+ result = retrieve("something") { called += 1; nil }
340
+ called.should == 1
341
+ result.should == nil
342
+ @last_error.should == "Could not find something"
343
+ result = retrieve("something else", :audit => true, :log => true) { called += 1 }
344
+ called.should == 2
345
+ @last_error.should be_nil
346
+ end
347
+ end
348
+
349
+ context :instance_token do
350
+ it 'should retrieve InstanceApiToken using id' do
351
+ flexmock(InstanceApiToken).should_receive(:find).with(123).and_return("instance_api_token").once
352
+ instance_token(123).should == "instance_api_token"
353
+ end
354
+ end
355
+
356
+ context :instance do
357
+ before(:each) do
358
+ @token_id = 123
359
+ @account = flexmock("account")
360
+ @account.should_receive(:disabled_in_every_shard?).and_return(false).by_default
361
+ @instance = flexmock("instance", :account => @account)
362
+ @instance_api_token = flexmock("instance_api_token")
363
+ @instance_api_token.should_receive(:instance).and_return(@instance)
364
+ flexmock(InstanceApiToken).should_receive(:find).with(@token_id).and_return(@instance_api_token).once
365
+ end
366
+
367
+ it 'should retrieve Instance using id' do
368
+ instance(@token_id).should == @instance
369
+ end
370
+
371
+ it 'should raise RetryableError if shard is disabled for account' do
372
+ @account.should_receive(:disabled_in_every_shard?).and_return(true)
373
+ lambda do
374
+ instance(@token_id)
375
+ end.should raise_error(RightScale::Exceptions::RetryableError)
376
+ end
377
+ end
378
+
379
+ context :instance_from_token_id do
380
+ before(:each) do
381
+ @token_id = 123
382
+ @account = flexmock("account")
383
+ @account.should_receive(:disabled_in_every_shard?).and_return(false).by_default
384
+ @instance = flexmock("instance", :account => @account)
385
+ @instance_api_token = flexmock("instance_api_token")
386
+ @instance_api_token.should_receive(:instance).and_return(@instance).by_default
387
+ flexmock(InstanceApiToken).should_receive(:find).with(@token_id).and_return(@instance_api_token).by_default
388
+ end
389
+
390
+ it 'should retrieve Instance using token_id' do
391
+ instance_from_token_id(@token_id).should == @instance
392
+ end
393
+
394
+ it 'should cache tokens and use to reload instance' do
395
+ @instance.should_receive(:reload).and_return(@instance).once
396
+ instance_from_token_id(@token_id).should == @instance
397
+ @tokens[@token_id].should == @instance
398
+ instance_from_token_id(@token_id).should == @instance
399
+ end
400
+
401
+ it 'should raise exception if InstanceApiToken not found' do
402
+ flexmock(InstanceApiToken).should_receive(:find).with(@token_id).and_return(nil)
403
+ lambda do
404
+ instance_from_token_id(@token_id).should == @instance
405
+ end.should raise_error(RightScale::Exceptions::Application)
406
+ end
407
+
408
+ it 'should raise exception if Instance not found' do
409
+ @instance_api_token.should_receive(:instance).and_return(nil)
410
+ lambda do
411
+ instance_from_token_id(@token_id).should == @instance
412
+ end.should raise_error(RightScale::Exceptions::Application)
413
+ end
414
+
415
+ it 'should raise RetryableError if shard is disabled for account' do
416
+ @account.should_receive(:disabled_in_every_shard?).and_return(true)
417
+ lambda do
418
+ instance_from_token_id(@token_id)
419
+ end.should raise_error(RightScale::Exceptions::RetryableError)
420
+ end
421
+ end
422
+
423
+ context :instance_from_agent_id do
424
+ before(:each) do
425
+ @token_id = 123
426
+ @agent_id = "rs-instance-1111-#{@token_id}"
427
+ @account = flexmock("account")
428
+ @account.should_receive(:disabled_in_every_shard?).and_return(false).by_default
429
+ @instance = flexmock("instance", :account => @account)
430
+ @instance_api_token = flexmock("instance_api_token")
431
+ @instance_api_token.should_receive(:instance).and_return(@instance).by_default
432
+ flexmock(InstanceApiToken).should_receive(:find).with(@token_id).and_return(@instance_api_token).by_default
433
+ end
434
+
435
+ it 'should retrieve Instance using agent identity' do
436
+ instance_from_agent_id(@agent_id).should == @instance
437
+ end
438
+
439
+ it 'should raise error if agent identity invalid' do
440
+ lambda do
441
+ instance_from_agent_id("rs-instance-1111")
442
+ end.should raise_error(ArgumentError)
443
+ end
444
+
445
+ it 'should raise RetryableError if shard is disabled for account' do
446
+ @account.should_receive(:disabled_in_every_shard?).and_return(true)
447
+ lambda do
448
+ instance_from_agent_id(@agent_id)
449
+ end.should raise_error(RightScale::Exceptions::RetryableError)
450
+ end
451
+ end
452
+
453
+ context :account do
454
+ it 'should retrieve Account using id' do
455
+ flexmock(Account).should_receive(:find).with(123).and_return("account").once
456
+ account(123).should == "account"
457
+ end
458
+ end
459
+
460
+ context :audit_entry do
461
+ it 'should retrieve AuditEntry using id' do
462
+ flexmock(AuditEntry).should_receive(:find).with(123).and_return("audit_entry").once
463
+ audit_entry(123).should == "audit_entry"
464
+ end
465
+ end
466
+
467
+ context :right_script do
468
+ it 'should retrieve RightScript using id' do
469
+ flexmock(RightScript).should_receive(:find).with(123).and_return("right_script").once
470
+ right_script(123).should == "right_script"
471
+ end
472
+ end
473
+
474
+ context :right_script_from_name do
475
+ it 'should retrieve RightScript using name' do
476
+ right_scripts = flexmock("right_scripts")
477
+ right_scripts.should_receive(:find_by_name).with("name").and_return("script")
478
+ server_template = flexmock("server_template", :right_scripts => right_scripts)
479
+ instance = flexmock("instance", :server_template => server_template)
480
+ right_script_from_name("name", instance).should == "script"
481
+ end
482
+
483
+ it 'should not retrieve RightScript if instance has no server template' do
484
+ instance = flexmock("instance", :server_template => nil)
485
+ right_script_from_name("name", instance).should be_nil
486
+ end
487
+ end
488
+
489
+ context :recipe do
490
+ it 'should retrieve ServerTemplateChefRecipe using id' do
491
+ flexmock(ServerTemplateChefRecipe).should_receive(:find).with(123).and_return("recipe").once
492
+ recipe(123).should == "recipe"
493
+ end
494
+ end
495
+
496
+ context :recipe_from_name do
497
+ it 'should retrieve ServerTemplateChefRecipe using name' do
498
+ server_template_chef_recipes = flexmock("server_template_chef_recipes")
499
+ server_template_chef_recipes.should_receive(:find_by_recipe).with("name").and_return("recipe")
500
+ server_template = flexmock("server_template", :server_template_chef_recipes => server_template_chef_recipes)
501
+ instance = flexmock("instance", :server_template => server_template)
502
+ recipe_from_name("name", instance).should == "recipe"
503
+ end
504
+
505
+ it 'should not retrieve ServerTemplateChefRecipe if instance has no server template' do
506
+ instance = flexmock("instance", :server_template => nil)
507
+ recipe_from_name("name", instance).should be_nil
508
+ end
509
+ end
510
+
511
+ context :permission do
512
+ it 'should retrieve Permission using id' do
513
+ flexmock(Permission).should_receive(:find).with(123).and_return("permission").once
514
+ permission(123).should == "permission"
515
+ end
516
+ end
517
+
518
+ context :user_credential do
519
+ it 'should retrieve UserCredential using id' do
520
+ flexmock(UserCredential).should_receive(:find).with(123).and_return("user_credential").once
521
+ user_credential(123).should == "user_credential"
522
+ end
523
+ end
524
+
525
+ context :user_credential_from_fingerprint do
526
+ it 'should retrieve UserCredential using fingerprint' do
527
+ flexmock(UserCredential).should_receive(:find_by_public_value_fingerprint).with("123").and_return("user_credential").once
528
+ user_credential_from_fingerprint("123").should == "user_credential"
529
+ end
530
+ end
531
+
532
+ context :setting do
533
+ it 'should retrieve Setting using id' do
534
+ flexmock(Setting).should_receive(:find).with(123).and_return("setting").once
535
+ setting(123).should == "setting"
536
+ end
537
+ end
538
+
539
+ context :repositories do
540
+ it 'should retrieve all Repository objects' do
541
+ flexmock(Repository).should_receive(:find).with(:all).and_return("repositories").once
542
+ repositories.should == "repositories"
543
+ end
544
+ end
545
+
546
+ end