right_agent 1.0.1 → 2.0.7
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.
- data/README.rdoc +10 -8
- data/Rakefile +31 -5
- data/lib/right_agent.rb +6 -1
- data/lib/right_agent/actor.rb +4 -20
- data/lib/right_agent/actors/agent_manager.rb +1 -1
- data/lib/right_agent/agent.rb +357 -144
- data/lib/right_agent/agent_config.rb +7 -6
- data/lib/right_agent/agent_identity.rb +13 -11
- data/lib/right_agent/agent_tag_manager.rb +60 -64
- data/{spec/results_mock.rb → lib/right_agent/clients.rb} +10 -24
- data/lib/right_agent/clients/api_client.rb +383 -0
- data/lib/right_agent/clients/auth_client.rb +247 -0
- data/lib/right_agent/clients/balanced_http_client.rb +369 -0
- data/lib/right_agent/clients/base_retry_client.rb +495 -0
- data/lib/right_agent/clients/right_http_client.rb +279 -0
- data/lib/right_agent/clients/router_client.rb +493 -0
- data/lib/right_agent/command/command_io.rb +4 -4
- data/lib/right_agent/command/command_parser.rb +2 -2
- data/lib/right_agent/command/command_runner.rb +1 -1
- data/lib/right_agent/connectivity_checker.rb +179 -0
- data/lib/right_agent/core_payload_types/secure_document_location.rb +2 -2
- data/lib/right_agent/dispatcher.rb +12 -10
- data/lib/right_agent/enrollment_result.rb +16 -12
- data/lib/right_agent/exceptions.rb +34 -20
- data/lib/right_agent/history.rb +10 -5
- data/lib/right_agent/log.rb +5 -5
- data/lib/right_agent/minimal.rb +1 -0
- data/lib/right_agent/multiplexer.rb +1 -1
- data/lib/right_agent/offline_handler.rb +270 -0
- data/lib/right_agent/packets.rb +7 -7
- data/lib/right_agent/payload_formatter.rb +1 -1
- data/lib/right_agent/pending_requests.rb +128 -0
- data/lib/right_agent/platform.rb +1 -1
- data/lib/right_agent/protocol_version_mixin.rb +69 -0
- data/lib/right_agent/{idempotent_request.rb → retryable_request.rb} +7 -7
- data/lib/right_agent/scripts/agent_controller.rb +28 -26
- data/lib/right_agent/scripts/agent_deployer.rb +37 -22
- data/lib/right_agent/scripts/common_parser.rb +10 -3
- data/lib/right_agent/secure_identity.rb +1 -1
- data/lib/right_agent/sender.rb +299 -785
- data/lib/right_agent/serialize/secure_serializer.rb +3 -1
- data/lib/right_agent/serialize/secure_serializer_initializer.rb +2 -2
- data/lib/right_agent/serialize/serializable.rb +8 -3
- data/right_agent.gemspec +49 -18
- data/spec/agent_config_spec.rb +7 -7
- data/spec/agent_identity_spec.rb +7 -4
- data/spec/agent_spec.rb +43 -7
- data/spec/agent_tag_manager_spec.rb +72 -83
- data/spec/clients/api_client_spec.rb +423 -0
- data/spec/clients/auth_client_spec.rb +272 -0
- data/spec/clients/balanced_http_client_spec.rb +576 -0
- data/spec/clients/base_retry_client_spec.rb +635 -0
- data/spec/clients/router_client_spec.rb +594 -0
- data/spec/clients/spec_helper.rb +111 -0
- data/spec/command/command_io_spec.rb +1 -1
- data/spec/command/command_parser_spec.rb +1 -1
- data/spec/connectivity_checker_spec.rb +83 -0
- data/spec/dispatcher_spec.rb +3 -2
- data/spec/enrollment_result_spec.rb +2 -2
- data/spec/history_spec.rb +51 -39
- data/spec/offline_handler_spec.rb +340 -0
- data/spec/pending_requests_spec.rb +136 -0
- data/spec/{idempotent_request_spec.rb → retryable_request_spec.rb} +73 -73
- data/spec/sender_spec.rb +835 -1052
- data/spec/serialize/secure_serializer_spec.rb +3 -2
- data/spec/spec_helper.rb +54 -1
- metadata +71 -12
@@ -0,0 +1,423 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
#--
|
4
|
+
# Copyright (c) 2013 RightScale Inc
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
# a copy of this software and associated documentation files (the
|
8
|
+
# "Software"), to deal in the Software without restriction, including
|
9
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
# the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be
|
15
|
+
# included in all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
21
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
22
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
23
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
24
|
+
#++
|
25
|
+
|
26
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
27
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib', 'right_agent', 'clients', 'api_client'))
|
28
|
+
|
29
|
+
describe RightScale::ApiClient do
|
30
|
+
|
31
|
+
include FlexMock::ArgumentTypes
|
32
|
+
|
33
|
+
before(:each) do
|
34
|
+
@log = flexmock(RightScale::Log)
|
35
|
+
@log.should_receive(:error).by_default.and_return { |m| raise RightScale::Log.format(*m) }
|
36
|
+
@log.should_receive(:warning).by_default.and_return { |m| raise RightScale::Log.format(*m) }
|
37
|
+
@timer = flexmock("timer", :cancel => true, :interval= => 0).by_default
|
38
|
+
flexmock(EM::PeriodicTimer).should_receive(:new).and_return(@timer).and_yield.by_default
|
39
|
+
@account_id = 123
|
40
|
+
@agent_id = "rs-instance-1-1"
|
41
|
+
@agent_href = "/api/clouds/1/instances/1"
|
42
|
+
@agent_href2 = "/api/clouds/2/instances/2"
|
43
|
+
@links = {"links" => [{"rel" => "self", "href" => @agent_href}]}
|
44
|
+
@http_client = flexmock("http client", :get => @links, :check_health => true).by_default
|
45
|
+
flexmock(RightScale::BalancedHttpClient).should_receive(:new).and_return(@http_client).by_default
|
46
|
+
@url = "http://test.com"
|
47
|
+
@auth_header = {"Authorization" => "Bearer <session>"}
|
48
|
+
@auth_client = AuthClientMock.new(@url, @auth_header, :authorized, @account_id, @agent_id)
|
49
|
+
@options = {}
|
50
|
+
@client = RightScale::ApiClient.new(@auth_client, @options)
|
51
|
+
@version = RightScale::AgentConfig.protocol_version
|
52
|
+
@payload = {:agent_identity => @agent_id}
|
53
|
+
@target = nil
|
54
|
+
@token = "random token"
|
55
|
+
end
|
56
|
+
|
57
|
+
context :initialize do
|
58
|
+
it "initializes options" do
|
59
|
+
@options = {
|
60
|
+
:open_timeout => 1,
|
61
|
+
:request_timeout => 2,
|
62
|
+
:listen_timeout => 3,
|
63
|
+
:retry_timeout => 4,
|
64
|
+
:retry_intervals => [1, 2, 3],
|
65
|
+
:reconnect_interval => 5 }
|
66
|
+
@client = RightScale::ApiClient.new(@auth_client, @options)
|
67
|
+
options = @client.instance_variable_get(:@options)
|
68
|
+
options[:server_name] = "RightApi"
|
69
|
+
options[:api_version] = "1.5"
|
70
|
+
options[:open_timeout] = 1
|
71
|
+
options[:request_timeout] = 2
|
72
|
+
options[:retry_timeout] = 3
|
73
|
+
options[:retry_intervals] = [1, 2, 3]
|
74
|
+
options[:reconnect_interval] = 4
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context :push do
|
79
|
+
it "makes mapped request" do
|
80
|
+
flexmock(@client).should_receive(:make_request).with(:post, "/audit_entries/111/append", {:detail => "details"},
|
81
|
+
"update_entry", @token, Hash).and_return(nil).once
|
82
|
+
@client.push("/auditor/update_entry", @payload.merge(:audit_id => 111, :detail => "details"), @target, @token).should be_nil
|
83
|
+
end
|
84
|
+
|
85
|
+
it "does not require token" do
|
86
|
+
flexmock(@client).should_receive(:make_request).with(:post, "/audit_entries/111/append", {:detail => "details"},
|
87
|
+
"update_entry", nil, Hash).and_return(nil).once
|
88
|
+
@client.push("/auditor/update_entry", @payload.merge(:audit_id => 111, :detail => "details"), @target).should be_nil
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context :request do
|
93
|
+
it "makes mapped request" do
|
94
|
+
flexmock(@client).should_receive(:make_request).with(:post, "/right_net/booter/declare", {:r_s_version => @version},
|
95
|
+
"declare", @token, Hash).and_return(nil).once
|
96
|
+
@client.push("/booter/declare", @payload.merge(:r_s_version => @version), @target, @token).should be_nil
|
97
|
+
end
|
98
|
+
|
99
|
+
it "maps query_tags request" do
|
100
|
+
flexmock(@client).should_receive(:map_query_tags).with(:post, {:tags => ["a:b=c"]}, "query_tags", @token, Hash).
|
101
|
+
and_return({}).once
|
102
|
+
@client.request("/router/query_tags", @payload.merge(:tags => ["a:b=c"]), @target, @token).should == {}
|
103
|
+
end
|
104
|
+
|
105
|
+
it "does not require token" do
|
106
|
+
flexmock(@client).should_receive(:make_request).with(:post, "/right_net/booter/declare", {:r_s_version => @version},
|
107
|
+
"declare", nil, Hash).and_return(nil).once
|
108
|
+
@client.push("/booter/declare", @payload.merge(:r_s_version => @version), @target).should be_nil
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context :support? do
|
113
|
+
it "returns true if request type is supported" do
|
114
|
+
@client.support?("/booter/declare").should be_true
|
115
|
+
end
|
116
|
+
|
117
|
+
it "returns false if request type is not supported" do
|
118
|
+
@client.support?("/instance_scheduler/execute").should be_false
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context :map_request do
|
123
|
+
it "raises if request type not supported" do
|
124
|
+
lambda { @client.send(:map_request, "/instance_scheduler/execute", @payload, @token) }.should \
|
125
|
+
raise_error(ArgumentError, "Unsupported request type: /instance_scheduler/execute")
|
126
|
+
end
|
127
|
+
|
128
|
+
it "makes request" do
|
129
|
+
flexmock(@client).should_receive(:make_request).with(:post, "/right_net/booter/declare", {:r_s_version => @version},
|
130
|
+
"declare", @token, Hash).and_return(nil).once
|
131
|
+
@client.send(:map_request, "/booter/declare", @payload.merge(:r_s_version => @version), @token).should be_nil
|
132
|
+
end
|
133
|
+
|
134
|
+
it "returns mapped response" do
|
135
|
+
flexmock(@client).should_receive(:make_request).with(:post, "/audit_entries",
|
136
|
+
{:audit_entry => {:auditee_href => @agent_href, :summary => "summary"}}, "create_entry", @token, Hash).
|
137
|
+
and_return("/api/audit_entries/111").once
|
138
|
+
@client.send(:map_request, "/auditor/create_entry", @payload.merge(:summary => "summary"), @token).should == "111"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
context :map_response do
|
143
|
+
it "converts audit entry href in result to an audit ID" do
|
144
|
+
response = "/api/audit_entries/111"
|
145
|
+
@client.send(:map_response, response, "/audit_entries").should == "111"
|
146
|
+
end
|
147
|
+
|
148
|
+
it "converts tag query result to list of tags for resource" do
|
149
|
+
response = [
|
150
|
+
{ "actions" => [],
|
151
|
+
"links" => [{"rel" => "resource", "href" => "/api/clouds/6/instances/CUPVAL7KUP7TF"}],
|
152
|
+
"tags" => [{"name" => "rs_agent_dev:log_level=debug"},
|
153
|
+
{"name" => "rs_login:state=restricted"},
|
154
|
+
{"name" => "rs_monitoring:state=active"}] },
|
155
|
+
{ "actions" => [],
|
156
|
+
"links" => [{"rel" => "resource", "href" => "/api/servers/20"}],
|
157
|
+
"tags" => [{"name" => "server:ready=now"},
|
158
|
+
{"name" => "rs_agent_dev:log_level=debug"}] } ]
|
159
|
+
@client.send(:map_response, response, "/tags/by_resource").should ==
|
160
|
+
{ "/api/clouds/6/instances/CUPVAL7KUP7TF" => { "tags" => ["rs_agent_dev:log_level=debug",
|
161
|
+
"rs_login:state=restricted",
|
162
|
+
"rs_monitoring:state=active"] } }
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
context "query tags" do
|
167
|
+
before(:each) do
|
168
|
+
@action = "query_tags"
|
169
|
+
@options = {}
|
170
|
+
@tags = ["a:b=c"]
|
171
|
+
@hrefs = [@agent_href2]
|
172
|
+
@response = [
|
173
|
+
{ "actions" => [],
|
174
|
+
"links" => [{"rel" => "resource", "href" => @agent_href}],
|
175
|
+
"tags" => [{"name" => "a:b=c"}, {"name" => "x:y=z"}] } ]
|
176
|
+
end
|
177
|
+
|
178
|
+
context :map_query_tags do
|
179
|
+
it "retrieves resource hrefs for specified tags" do
|
180
|
+
params = {:tags => @tags}
|
181
|
+
params2 = params.merge(:match_all => false, :resource_type => "instances")
|
182
|
+
flexmock(@client).should_receive(:query_by_resource).never
|
183
|
+
flexmock(@client).should_receive(:make_request).with(:post, "/tags/by_tag", params2, @action, @token, @options).and_return({}).once
|
184
|
+
@client.send(:map_query_tags, :post, params, @action, @token, @options).should == {}
|
185
|
+
end
|
186
|
+
|
187
|
+
it "appends retrieved hrefs to any specified resource hrefs" do
|
188
|
+
params = {:tags => @tags, :resource_hrefs => @hrefs}
|
189
|
+
params2 = {:resource_hrefs => [@agent_href2, @agent_href]}
|
190
|
+
flexmock(@client).should_receive(:query_by_tag).with(:post, @tags, @action, @token, @options).and_return([@agent_href]).once
|
191
|
+
flexmock(@client).should_receive(:make_request).with(:post, "/tags/by_resource", params2, @action, @token, @options).and_return({}).once
|
192
|
+
@client.send(:map_query_tags, :post, params, @action, @token, @options).should == {}
|
193
|
+
end
|
194
|
+
|
195
|
+
it "queries for tags for each resource href" do
|
196
|
+
params = {:resource_hrefs => @hrefs}
|
197
|
+
flexmock(@client).should_receive(:query_by_tag).never
|
198
|
+
flexmock(@client).should_receive(:make_request).with(:post, "/tags/by_resource", params, @action, @token, @options).and_return(@response).once
|
199
|
+
@client.send(:map_query_tags, :post, params, @action, @token, @options).should == {@agent_href => {"tags" => ["a:b=c", "x:y=z"]}}
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
context :query_by_tag do
|
204
|
+
before(:each) do
|
205
|
+
@params = {:tags => @tags}
|
206
|
+
@params2 = @params.merge(:match_all => false, :resource_type => "instances")
|
207
|
+
end
|
208
|
+
|
209
|
+
it "queries for tags using specified tags" do
|
210
|
+
flexmock(@client).should_receive(:make_request).with(:post, "/tags/by_tag", @params2, @action, @token, @options).and_return({}).once
|
211
|
+
@client.send(:query_by_tag, :post, @tags, @action, @token, @options).should == []
|
212
|
+
end
|
213
|
+
|
214
|
+
it "maps response" do
|
215
|
+
flexmock(@client).should_receive(:make_request).with(:post, "/tags/by_tag", @params2, @action, @token, @options).and_return(@response).once
|
216
|
+
@client.send(:query_by_tag, :post, @tags, @action, @token, @options).should == [@agent_href]
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
context :query_by_resource do
|
221
|
+
before(:each) do
|
222
|
+
@params = {:resource_hrefs => @hrefs}
|
223
|
+
end
|
224
|
+
|
225
|
+
it "queries for tags using specified hrefs" do
|
226
|
+
flexmock(@client).should_receive(:make_request).with(:post, "/tags/by_resource", @params, @action, @token, @options).and_return({}).once
|
227
|
+
@client.send(:query_by_resource, :post, @hrefs, @action, @token, @options).should == {}
|
228
|
+
end
|
229
|
+
|
230
|
+
it "maps response" do
|
231
|
+
flexmock(@client).should_receive(:make_request).with(:post, "/tags/by_resource", @params, @action, @token, @options).and_return(@response).once
|
232
|
+
@client.send(:query_by_resource, :post, @hrefs, @action, @token, @options).should == {@agent_href => {"tags" => ["a:b=c", "x:y=z"]}}
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
context :parameterize do
|
238
|
+
|
239
|
+
context "for audits" do
|
240
|
+
before(:each) do
|
241
|
+
@path, @params, @options = @client.send(:parameterize, "auditor", "update_entry", @payload.merge(:audit_id => 111,
|
242
|
+
:detail => "details"), "/audit_entries/:id/append")
|
243
|
+
end
|
244
|
+
|
245
|
+
it "converts audit parameters" do
|
246
|
+
@params.should == {:detail => "details"}
|
247
|
+
end
|
248
|
+
|
249
|
+
it "substitutes audit ID into path" do
|
250
|
+
@path.should == "/audit_entries/111/append"
|
251
|
+
end
|
252
|
+
|
253
|
+
it "adds parameter filter to options" do
|
254
|
+
@options.should == {:filter_params => ["detail", "text"]}
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
context "for tags" do
|
259
|
+
before(:each) do
|
260
|
+
tags = ["a:b=c", nil, [nil, "x:y=z"]]
|
261
|
+
@path, @params, @options = @client.send(:parameterize, "router", "add_tags", @payload.merge(:tags => tags),
|
262
|
+
"/tags/multi_add")
|
263
|
+
end
|
264
|
+
|
265
|
+
it "adds default resource href to parameters" do
|
266
|
+
@params[:resource_hrefs].should == [@agent_href]
|
267
|
+
end
|
268
|
+
|
269
|
+
it "adds specified resource hrefs to parameters" do
|
270
|
+
hrefs = [@agent_href, @agent_href2]
|
271
|
+
_, @params, _ = @client.send(:parameterize, "router", "query_tags", @payload.merge(:hrefs => hrefs),
|
272
|
+
"/tags/by_resource")
|
273
|
+
@params[:resource_hrefs].should == hrefs
|
274
|
+
end
|
275
|
+
|
276
|
+
it "ensures that tags parameter is properly formed" do
|
277
|
+
@params[:tags].should == ["a:b=c", "x:y=z"]
|
278
|
+
end
|
279
|
+
|
280
|
+
it "does not add tags if not present" do
|
281
|
+
_, @params, _ = @client.send(:parameterize, "router", "query_tags", @payload, "/tags/multi_add")
|
282
|
+
@params.should_not have_key(:tags)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
context "otherwise" do
|
287
|
+
before(:each) do
|
288
|
+
@path, @params, @options = @client.send(:parameterize, "booter", "declare", @payload.merge(:r_s_version => @version),
|
289
|
+
"/right_net/booter/declare")
|
290
|
+
end
|
291
|
+
|
292
|
+
it "removes :agent_identity parameter" do
|
293
|
+
@params[:agent_identity].should be_nil
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
context :parameterize_audit do
|
299
|
+
|
300
|
+
context "create_entry" do
|
301
|
+
it "stores instance href" do
|
302
|
+
@client.send(:parameterize_audit, "create_entry", @payload)[:audit_entry][:auditee_href].should == @agent_href
|
303
|
+
end
|
304
|
+
|
305
|
+
context "summary" do
|
306
|
+
it "stores summary if non-blank" do
|
307
|
+
@client.send(:parameterize_audit, "create_entry", :summary => "hello")[:audit_entry][:summary].should == "hello"
|
308
|
+
@client.send(:parameterize_audit, "create_entry", :summary => "")[:audit_entry].should_not have_key(:summary)
|
309
|
+
@client.send(:parameterize_audit, "create_entry", {})[:audit_entry].should_not have_key(:summary)
|
310
|
+
end
|
311
|
+
|
312
|
+
it "truncates summary if too long" do
|
313
|
+
@client.send(:parameterize_audit, "create_entry", :summary => "hello " * 50)[:audit_entry][:summary].size.should == 255
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
it "stores detail if non-blank" do
|
318
|
+
@client.send(:parameterize_audit, "create_entry", :detail => "details")[:audit_entry][:detail].should == "details"
|
319
|
+
@client.send(:parameterize_audit, "create_entry", :detail => "")[:audit_entry].should_not have_key(:detail)
|
320
|
+
@client.send(:parameterize_audit, "create_entry", {})[:audit_entry].should_not have_key(:detail)
|
321
|
+
end
|
322
|
+
|
323
|
+
it "stores user email if non-blank" do
|
324
|
+
@client.send(:parameterize_audit, "create_entry", :user_email => "email")[:user_email].should == "email"
|
325
|
+
@client.send(:parameterize_audit, "create_entry", :user_email => "").should_not have_key(:user_email)
|
326
|
+
@client.send(:parameterize_audit, "create_entry", {}).should_not have_key(:user_email)
|
327
|
+
end
|
328
|
+
|
329
|
+
it "stores notify category if present" do
|
330
|
+
@client.send(:parameterize_audit, "create_entry", :category => "Notification")[:notify].should == "Notification"
|
331
|
+
@client.send(:parameterize_audit, "create_entry", {}).should_not have_key(:notify)
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
context "update_entry" do
|
336
|
+
it "stores offset if present" do
|
337
|
+
@client.send(:parameterize_audit, "update_entry", :offset => 100)[:offset].should == 100
|
338
|
+
@client.send(:parameterize_audit, "update_entry", {}).should_not have_key(:offset)
|
339
|
+
end
|
340
|
+
|
341
|
+
context "summary" do
|
342
|
+
it "stores summary if non-blank" do
|
343
|
+
@client.send(:parameterize_audit, "update_entry", :summary => "hello")[:summary].should == "hello"
|
344
|
+
@client.send(:parameterize_audit, "update_entry", :summary => "").should_not have_key(:summary)
|
345
|
+
@client.send(:parameterize_audit, "update_entry", {}).should_not have_key(:summary)
|
346
|
+
end
|
347
|
+
|
348
|
+
it "truncates summary if too long" do
|
349
|
+
@client.send(:parameterize_audit, "update_entry", :summary => "hello " * 50)[:summary].size.should == 255
|
350
|
+
end
|
351
|
+
|
352
|
+
it "stores notify category if present and summary is non-blank" do
|
353
|
+
@client.send(:parameterize_audit, "update_entry", :summary => "hello", :category => "Notification")[:notify].should == "Notification"
|
354
|
+
@client.send(:parameterize_audit, "update_entry", :category => "Notification").should_not have_key(:notify)
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
it "stores detail if non-blank" do
|
359
|
+
@client.send(:parameterize_audit, "update_entry", :detail => "details")[:detail].should == "details"
|
360
|
+
@client.send(:parameterize_audit, "update_entry", :detail => "").should_not have_key(:detail)
|
361
|
+
@client.send(:parameterize_audit, "update_entry", {}).should_not have_key(:detail)
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
context "otherwise" do
|
366
|
+
it "raises unknown audit" do
|
367
|
+
lambda { @client.send(:parameterize_audit, "bogus", {}) }.should raise_error(ArgumentError)
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
context :truncate do
|
373
|
+
it "returns non-string as is" do
|
374
|
+
@client.send(:truncate, nil, 5).should == nil
|
375
|
+
end
|
376
|
+
|
377
|
+
it "returns strings shorter than limit as is" do
|
378
|
+
@client.send(:truncate, "hello", 5).should == "hello"
|
379
|
+
end
|
380
|
+
|
381
|
+
it "requires max length to be greater than 3" do
|
382
|
+
@client.send(:truncate, "hello", 4).should == "h..."
|
383
|
+
lambda { @client.send(:truncate, "hello", 3) }.should raise_error(ArgumentError)
|
384
|
+
end
|
385
|
+
|
386
|
+
it "truncates strings that are too long" do
|
387
|
+
@client.send(:truncate, "how you doing", 10).bytesize.should == 10
|
388
|
+
end
|
389
|
+
|
390
|
+
it "ends truncated string with an ellipsis" do
|
391
|
+
@client.send(:truncate, "how you doing", 10).should == "how you..."
|
392
|
+
end
|
393
|
+
|
394
|
+
it "accounts for multi-byte characters" do
|
395
|
+
@client.send(:truncate, "Schös Tägli wünschi", 20).should == "Schös Tägli wü..."
|
396
|
+
@client.send(:truncate, "Schös Tägli wünschi", 19).should == "Schös Tägli w..."
|
397
|
+
@client.send(:truncate, "Schös Tägli wünschi", 18).should == "Schös Tägli w..."
|
398
|
+
@client.send(:truncate, "Schös Tägli wünschi", 17).should == "Schös Tägli ..."
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
context :non_blank do
|
403
|
+
it "returns nil if value nil" do
|
404
|
+
@client.send(:non_blank, nil).should be_nil
|
405
|
+
end
|
406
|
+
|
407
|
+
it "returns nil if value is empty" do
|
408
|
+
@client.send(:non_blank, "").should be_nil
|
409
|
+
end
|
410
|
+
|
411
|
+
it "returns nil if value nil" do
|
412
|
+
@client.send(:non_blank, "hello").should == "hello"
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
context :enable_use do
|
417
|
+
it "makes API request to get links for setting instance href" do
|
418
|
+
flexmock(@client).should_receive(:make_request).with(:get, "/sessions/instance", {}, "instance").and_return(@links).once
|
419
|
+
@client.instance_variable_get(:@self_href).should == @agent_href
|
420
|
+
@client.send(:enable_use).should be_true
|
421
|
+
end
|
422
|
+
end
|
423
|
+
end
|
@@ -0,0 +1,272 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
25
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib', 'right_agent', 'clients', 'auth_client'))
|
26
|
+
|
27
|
+
class AuthClientTest < RightScale::AuthClient
|
28
|
+
def initialize(options = {})
|
29
|
+
@identity = options[:identity]
|
30
|
+
@api_url = options[:api_url]
|
31
|
+
@router_url = options[:router_url]
|
32
|
+
@account_id = options[:account_id]
|
33
|
+
@mode = options[:mode]
|
34
|
+
@access_token = options[:access_token]
|
35
|
+
@state = :pending
|
36
|
+
@status_callbacks = []
|
37
|
+
reset_stats
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe RightScale::AuthClient do
|
42
|
+
|
43
|
+
include FlexMock::ArgumentTypes
|
44
|
+
|
45
|
+
before(:each) do
|
46
|
+
@log = flexmock(RightScale::Log)
|
47
|
+
@log.should_receive(:error).by_default.and_return { |m| raise RightScale::Log.format(*m) }
|
48
|
+
@log.should_receive(:warning).by_default.and_return { |m| raise RightScale::Log.format(*m) }
|
49
|
+
@identity = "rs-agent-1-1"
|
50
|
+
@api_url = "http://api.com"
|
51
|
+
@router_url = "http://router.com"
|
52
|
+
@options = {
|
53
|
+
:identity => @identity,
|
54
|
+
:api_url => @api_url,
|
55
|
+
:router_url => @router_url,
|
56
|
+
:account_id => 123,
|
57
|
+
:mode => :http,
|
58
|
+
:access_token => "<test access token>" }
|
59
|
+
@client = AuthClientTest.new(@options)
|
60
|
+
end
|
61
|
+
|
62
|
+
context :initialize do
|
63
|
+
it "raises if abstract class" do
|
64
|
+
lambda { RightScale::AuthClient.new }.should raise_error(NotImplementedError, "RightScale::AuthClient is an abstract class")
|
65
|
+
end
|
66
|
+
|
67
|
+
it "does not raise for derived class" do
|
68
|
+
AuthClientTest.new
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context :identity do
|
73
|
+
it "returns identity" do
|
74
|
+
@client.identity.should == @identity
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context :headers do
|
79
|
+
it "raises if not authorized" do
|
80
|
+
lambda { @client.headers }.should raise_error(RightScale::Exceptions::Unauthorized)
|
81
|
+
end
|
82
|
+
|
83
|
+
it "returns headers" do
|
84
|
+
@client.send(:state=, :authorized)
|
85
|
+
@client.headers.should == {"Authorization" => "Bearer <test access token>"}
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context :auth_header do
|
90
|
+
it "raises if not authorized" do
|
91
|
+
lambda { @client.auth_header }.should raise_error(RightScale::Exceptions::Unauthorized)
|
92
|
+
end
|
93
|
+
|
94
|
+
it "returns authorization header" do
|
95
|
+
@client.send(:state=, :authorized)
|
96
|
+
@client.auth_header.should == {"Authorization" => "Bearer <test access token>"}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context :account_id do
|
101
|
+
it "raises if not authorized" do
|
102
|
+
lambda { @client.account_id }.should raise_error(RightScale::Exceptions::Unauthorized)
|
103
|
+
end
|
104
|
+
|
105
|
+
it "returns auth header" do
|
106
|
+
@client.send(:state=, :authorized)
|
107
|
+
@client.account_id.should == 123
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context :api_url do
|
112
|
+
it "raises if not authorized" do
|
113
|
+
lambda { @client.api_url }.should raise_error(RightScale::Exceptions::Unauthorized)
|
114
|
+
end
|
115
|
+
|
116
|
+
it "returns auth header" do
|
117
|
+
@client.send(:state=, :authorized)
|
118
|
+
@client.api_url.should == @api_url
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context :router_url do
|
123
|
+
it "raises if not authorized" do
|
124
|
+
lambda { @client.router_url }.should raise_error(RightScale::Exceptions::Unauthorized)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "returns auth header" do
|
128
|
+
@client.send(:state=, :authorized)
|
129
|
+
@client.router_url.should == @router_url
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context :mode do
|
134
|
+
it "returns mode" do
|
135
|
+
@client.mode.should == :http
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
context :expired do
|
140
|
+
it "logs that authorization expired" do
|
141
|
+
@log.should_receive(:info).with(/Renewing authorization/).once
|
142
|
+
@client.send(:state=, :authorized)
|
143
|
+
@client.expired.should be_true
|
144
|
+
end
|
145
|
+
|
146
|
+
it "sets state to :expired" do
|
147
|
+
@client.send(:state=, :authorized)
|
148
|
+
@client.expired.should be_true
|
149
|
+
@client.state.should == :expired
|
150
|
+
end
|
151
|
+
|
152
|
+
it "should renew authorization" do
|
153
|
+
@client.send(:state=, :authorized)
|
154
|
+
flexmock(@client).should_receive(:renew_authorization).once
|
155
|
+
@client.expired.should be_true
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
context :redirect do
|
160
|
+
it "handles redirect" do
|
161
|
+
@client.redirect("location").should be_true
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
context :close do
|
166
|
+
it "should set state to :closed" do
|
167
|
+
@client.close.should be_true
|
168
|
+
@client.state.should == :closed
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
context :status do
|
173
|
+
it "stores callback" do
|
174
|
+
callback = lambda { |_, _| }
|
175
|
+
@client.instance_variable_get(:@status_callbacks).size.should == 0
|
176
|
+
@client.status(&callback)
|
177
|
+
@client.instance_variable_get(:@status_callbacks).size.should == 1
|
178
|
+
@client.instance_variable_get(:@status_callbacks)[0].should == callback
|
179
|
+
end
|
180
|
+
|
181
|
+
it "treats callback as optional" do
|
182
|
+
@client.instance_variable_get(:@status_callbacks).size.should == 0
|
183
|
+
@client.status
|
184
|
+
@client.instance_variable_get(:@status_callbacks).size.should == 0
|
185
|
+
end
|
186
|
+
|
187
|
+
it "returns current state" do
|
188
|
+
@client.status.should == :pending
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
context :check_authorized do
|
193
|
+
it "raises retryable error if state is :expired" do
|
194
|
+
@client.send(:state=, :authorized)
|
195
|
+
@client.send(:expired)
|
196
|
+
lambda { @client.send(:check_authorized) }.should raise_error(RightScale::Exceptions::RetryableError, "Authorization expired")
|
197
|
+
end
|
198
|
+
|
199
|
+
it "raises unauthorized if state is not :authorized" do
|
200
|
+
lambda { @client.send(:check_authorized) }.should raise_error(RightScale::Exceptions::Unauthorized, "Not authorized with RightScale")
|
201
|
+
end
|
202
|
+
|
203
|
+
it "returns true if authorized" do
|
204
|
+
@client.send(:state=, :authorized)
|
205
|
+
@client.send(:check_authorized).should be_true
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
context :renew_authorization do
|
210
|
+
it "renews authorization" do
|
211
|
+
@client.send(:renew_authorization).should be_true
|
212
|
+
end
|
213
|
+
|
214
|
+
it "waits to renew authorization" do
|
215
|
+
@client.send(:renew_authorization, 10).should be_true
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
context :state= do
|
220
|
+
it "raises exception if state transition is invalid" do
|
221
|
+
lambda { @client.send(:state=, :expired) }.should raise_error(ArgumentError, "Invalid state transition: :pending -> :expired")
|
222
|
+
end
|
223
|
+
|
224
|
+
[:pending, :closed].each do |state|
|
225
|
+
context state do
|
226
|
+
it "stores new state" do
|
227
|
+
@client.send(:state=, state)
|
228
|
+
@client.state.should == state
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
[:authorized, :unauthorized, :expired, :failed].each do |state|
|
234
|
+
context state do
|
235
|
+
before(:each) do
|
236
|
+
@client.send(:state=, :authorized) if state == :expired
|
237
|
+
end
|
238
|
+
|
239
|
+
it "stores new state" do
|
240
|
+
@client.send(:state=, state)
|
241
|
+
@client.state.should == state
|
242
|
+
end
|
243
|
+
|
244
|
+
it "stores new state" do
|
245
|
+
@client.send(:state=, state).should == state
|
246
|
+
end
|
247
|
+
|
248
|
+
context "when callbacks" do
|
249
|
+
it "makes callbacks with new state" do
|
250
|
+
callback_type = callback_state = nil
|
251
|
+
@client.status { |t, s| callback_type = t; callback_state = s }
|
252
|
+
@client.send(:state=, state)
|
253
|
+
callback_type.should == :auth
|
254
|
+
callback_state.should == state
|
255
|
+
end
|
256
|
+
|
257
|
+
it "log error if callback fails" do
|
258
|
+
@log.should_receive(:error).with("Failed status callback", StandardError).once
|
259
|
+
@client.status { |t, s| raise StandardError, "test" }
|
260
|
+
@client.send(:state=, state).should == state
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
it "does nothing if current state is the same" do
|
265
|
+
flexmock(@client.instance_variable_get(:@stats)["state"]).should_receive(:update).once
|
266
|
+
@client.send(:state=, state)
|
267
|
+
@client.send(:state=, state).should == state
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|