right_infrastructure_agent 1.1.2

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